npm package: @mridang/commitlint-plugin-conditionals
Description: Semantic Release plugin that prevents major releases unless a corresponding tag exists on a specific branch in an upstream GitHub repository.
Author: Mridang Agarwalla
Latest version: 1.3.0

# Commitlint - Conditional Rules Plugin

A [commitlint](https://commitlint.js.org/)
plugin to enable conditional application of linting rules based on various
commit attributes, such as author patterns or message content.

This plugin helps ensure that commit message conventions can be flexibly
enforced. For instance, it allows for different linting standards for
human-authored commits versus automated commits (e.g., from bots), or for
varying rules based on the type of changes being committed. It is designed
to be extended with more conditional capabilities over time.

### Why?

Maintaining consistent and meaningful commit messages is crucial for project
health, enabling better collaboration, clearer history, and automated
changelog generation. [Commitlint](https://commitlint.js.org/) is an excellent
tool for enforcing these conventions.

However, a one-size-fits-all linting approach isn't always optimal.
Different types of commits or contributions might warrant different levels of
scrutiny or slightly varied rules:

- **Automated Commits:** Commits generated by bots (e.g., Dependabot,
  Renovate, CI scripts) often have a standardized, machine-generated format.
  Applying the same strict linting rules designed for human contributors can
  be overly restrictive or lead to unnecessary failures for these automated
  processes. It's often desirable to either bypass linting for these bots or
  apply a more lenient set of rules.
- **Work-in-Progress or Specific Branch Types:** Teams might want to enforce
  strict rules on main development lines (`main`, `develop`) but allow more
  flexibility on feature branches or for commits marked as "WIP" (Work In
  Progress).
- **Evolving Standards:** As a project evolves, you might want to introduce
  new linting rules but apply them conditionally at first, or only to
  certain types of commits, before making them universal.

This plugin, `@mridang/commitlint-plugin-conditionals`, aims to provide the
flexibility to handle such scenarios. It allows you to define conditions
(initially based on author patterns) under which commit linting rules are
applied differently, or even skipped entirely. This helps maintain high
standards for human contributions where needed, while accommodating the
practicalities of automated systems and diverse development workflows,
ultimately leading to a more effective and less burdensome commit linting
process.

## Installation

Install using NPM by using the following command:

```sh
npm install --save @mridang/commitlint-plugin-conditionals
```

## Usage

To use this plugin, add `@mridang/commitlint-plugin-conditionals` to your `commitlint`
configuration file (e.g., `commitlint.config.js` or `.commitlintrc.js`).

You'll need to list it in the `plugins` array. The rules provided by this
plugin will then be available to be configured in the `rules` object, prefixed
with the plugin's name.

First, add the plugin to the `plugins` array in your `commitlint` configuration:

```javascript
/**
 * @type {import('@commitlint/types').UserConfig}
 */
const config = {
  extends: [
    '@commitlint/config-conventional', // Or your preferred base configuration
  ],
  plugins: [
    '@mridang/conditionals', // Add the plugin by its package name
  ],
  rules: {
    // Rule configuration will go here
  },
};

module.exports = config;
```

#### Configuring the `ignoreForAuthors` Rule

This rule is the core of the plugin.

**Rule Name:** `@mridang/commitlint-plugin-conditionals/ignore-for-authors`

**Options (`IgnoreForAuthorsRuleOptions`):**

The rule accepts an object with the following properties:

- `ignoreAuthorPatterns?: string[]`:

  - An array of string patterns (e.g., `['dependabot[bot]', 'renovate-bot']`).
  - If a pattern matches content in the raw commit message, the rules
    defined in `rulesToEnforce` are bypassed for that commit.

- `rulesToEnforce?: ExternalRuleToEnforce[]`:
  - An array of rule configurations to apply if the commit is **not** from an
    ignored author.
  - Each entry is an object:
    - `packageName: string`: NPM package name (e.g., `'@commitlint/rules'`).
    - `ruleName: string`: Rule name within that package (e.g., `'type-enum'`).
    - `value: unknown`: Configuration value for that rule.

**Behavior:**

1.  If `ignoreAuthorPatterns` match, this rule passes, and its configured
    `rulesToEnforce` are skipped.
2.  If no `ignoreAuthorPatterns` match, each rule in `rulesToEnforce` is
    dynamically loaded and executed. If any fail, this rule fails.
3.  This rule is **asynchronous**.

**Important:** This rule only controls the execution of rules listed in its
`rulesToEnforce` option. Other rules from `extends` or elsewhere in your
main `rules` object will still apply to all commits unless explicitly
disabled or managed by a global dynamic configuration.

##### Example: Conditional Rules for Specific Authors

This example shows how to use `ignoreForAuthors` to apply different `body-max-line-length`
and `type-enum` settings for `dependabot[bot]` versus other users, while
still inheriting other rules from `@commitlint/config-conventional`.

```javascript
import { RuleConfigSeverity } from '@commitlint/types';
import mridangConditionalsPlugin from '@mridang/commitlint-plugin-conditionals'; // eslint-disable-line

/**
 * @type {import('@commitlint/types').UserConfig}
 */
export default {
  extends: ['@commitlint/config-conventional'],
  plugins: [mridangConditionalsPlugin],
  rules: {
    /**
     * Disable the 'body-max-line-length' rule from '@commitlint/config-conventional'
     * because we want our plugin to control it conditionally.
     * Using RuleConfigSeverity.Disabled (which is 0).
     */
    'body-max-line-length': [RuleConfigSeverity.Disabled, 'always'],

    /**
     * Configure the rule from your plugin.
     * The rule name is prefixed with your actual package name.
     * Options type (for JSDoc reference if needed):
     * import('@mridang/commitlint-conditionals').IgnoreForAuthorsRuleOptions
     */
    '@mridang/commitlint-conditionals/ignoreForAuthors': [
      RuleConfigSeverity.Error, // Severity: Error (2) for this conditional rule itself
      'always', // Condition
      {
        /**
         * Use 'signOffPatternsToIgnore' to specify patterns within
         * 'Signed-off-by:' lines.
         */
        signOffPatternsToIgnore: [
          'dependabot[bot]',
          // Add other patterns like 'bot@example.com'
        ],
        rulesToEnforce: [
          // For commits not matching signOffPatternsToIgnore, enforce this:
          {
            packageName: '@commitlint/rules',
            ruleName: 'body-max-line-length',
            value: 100, // Standard limit for humans
          },
          // Example: Enforcing type-enum for non-bots
          // {
          //   packageName: '@commitlint/rules',
          //   ruleName: 'type-enum',
          //   value: [
          //     'build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf',
          //     'refactor', 'revert', 'style', 'test',
          //   ],
          // },
        ],
      },
    ],
    /**
     * Note:
     * - Other rules from '@commitlint/config-conventional' (e.g., 'type-enum'
     * if not handled by the plugin above and its original disabled)
     * that were NOT disabled will now apply to ALL commits.
     * - This example makes 'body-max-line-length' conditional.
     */
  },
};
```

## Known Issues

- **External Rule Dependencies (`rulesToEnforce`):** When configuring `rulesToEnforce`, ensure each specified `packageName` is installed in your project and exports its rule functions in a standard `commitlint` plugin format. The plugin will dynamically load these external rules.
- **Author Identification (`signOffPatternsToIgnore`):** This feature identifies authors by searching for your specified patterns within `Signed-off-by:` lines in the raw commit message text. It does not directly use Git's discrete author fields (e.g., `GIT_AUTHOR_NAME`).
- **Plugin's Scope of Control:** This plugin conditionally applies only those rules explicitly listed in its `rulesToEnforce` option. It does **not** automatically bypass other rules inherited via `extends` or defined elsewhere in your main `commitlint` configuration. For a global bypass of all rules for certain authors, a dynamic JavaScript/TypeScript configuration file (`commitlint.config.js` or `.ts`) is necessary, as detailed in the "Usage" section.

## Useful links

- **[Commitlint](https://commitlint.js.org/):** The linting tool for commit
  messages that `@mridang/commitlint-plugin-conditionals` integrates with.
- **[Conventional Commits](https://www.conventionalcommits.org/):** The commit
  convention that `commitlint` often enforces.

## Contributing

If you have suggestions for how this app could be improved, or
want to report a bug, open an issue - we'd love all and any
contributions.

## License

Apache License 2.0 © 2024 Mridang Agarwalla