Shared Analysis Configuration

This document outlines the capabilities available in Qlty CLI and Qlty Cloud to support sharing of analysis configuration across multiple repositories within a single workspace.

Goals

  • Simplify the set up and maintenance of static analysis configuration across a large number of GitHub repositories
  • Provide partial or full standardization of static analysis configuration for the organization

Custom Sources

The primary method of sharing analysis configuration across multiple repositories is through defining a custom Source. A Source is a Git repository that stores version controlled static analysis configuration settings which can be re-used by other repositories.

Custom Sources were designed to replace the delegated configuration feature of Code Climate Quality while adding new capabilities.

Features of Custom Sources

  • Create and share new, custom Plugin Definitions
    • Example: Define a custom plugin to check that all package.json files specify an acceptable license value
  • Customize out-of-the-box Plugin Definitions
    • Example: Override the default definition of the Prettier auto-formatter to avoid rewriting *.mdx files
  • Share maintainability analysis settings
    • Example: Increase the complexity threshold for the “High Function Complexity” code smell
    • Example: Add a code filter to exclude common boilerplate from an internally developed framework from duplication detection
  • Share file path exclusions
    • Example: Exclude all files matching **/our_generator/**
  • Share issue triage rules
    • Example: Downgrade the level of issues found by Trivy in the sandbox/** folder to low
  • Automatically enable plugins
    • Example: Always run the Shellcheck for any shell scripts in all repositories
  • Share linter configuration files
    • Example: Provide a base rubocop.yml configuration that can be extended for each project

Advantages of Custom Sources

  • Settings provided by the Source can be further customized with the Project’s qlty.toml configuration file (layered configuration)
  • Sources can be declared as pinned (with Git tags) or floating to a branch
  • Custom sources work on Qlty CLI and Qlty Cloud

Example

To use a Custom Source, each repository would declare it from the .qlty/qlty.toml file:

1config_version = "0"
2
3# First, use the built-in Qlty source
4[[source]]
5name = "default"
6default = true
7
8# Customize further with the organization's custom source
9[[source]]
10name = "AcmeCo"
11repository = "https://github.com/AcmeCo/qlty-source"
12branch = "main"

Why are both sources needed? The default source provides all the built-in Qlty plugin definitions. Your custom source adds overrides and customizations on top. Without the default source, Qlty wouldn’t know how to run the standard plugins.

To pin your source to a specific version instead of floating to a branch, use tag:

1[[source]]
2name = "AcmeCo"
3repository = "https://github.com/AcmeCo/qlty-source"
4tag = "v1.0.0"

Source Repository Structure

The custom Source is a regular Git repository which follows a conventional structure:

your-qlty-source/
├── source.toml # Main configuration file
├── .eslintrc.js # Linter config files to share (optional)
├── rubocop.yml #
└── plugins/ # Custom plugin definitions (optional)
└── linters/
└── my-custom-plugin/
└── plugin.toml
  • A source.toml file at the root of the repository provides settings
  • Plugins can be defined or modified in files matching the patterns plugins/linters/*/plugin.toml
  • Linter configuration files can be placed at the root to be shared with child repos via exported_config_paths

Configuration Merging

Notably, Qlty uses the same TOML configuration syntax in every configuration file. This allows for taking advantage of the same configuration options — from plugin declaration settings to tuning maintainability smells — in any *.toml file read by Qlty.

Configuration is applied in the following order from lowest precedence to highest precedence:

  1. plugin.toml files in Sources
  2. source.toml file at the root of Sources
  3. qlty.toml in the project

As the final, merged configuration is assembled, the last setting wins.

The full, merged configuration can be viewed as YAML by running qlty config show.

Sharing Linter Configuration Files

Linter plugins often use their own configuration files (e.g., .eslintrc.js, rubocop.yml, .prettierrc). You can share these files from your custom source repository so that all child repos use the same configuration.

To share a linter configuration file:

  1. Commit the linter configuration file to your custom source repository.
  2. Within the source.toml, use the exported_config_paths property to specify which files should be copied to child repos at runtime.
1[plugins.definitions.<plugin-name>]
2exported_config_paths = ["<config-file>"]

How it works: When Qlty runs analysis on a child repo, it copies the files listed in exported_config_paths from your source repository into the child repo’s working directory. The linter then finds and uses these configuration files as if they were committed directly to the child repo.

If exported_config_paths is not set, config files in the source repo are NOT shared. The linter will use config files from the child repo if present, otherwise it falls back to its defaults.

config_files vs exported_config_paths

These two properties work together but serve different purposes:

PropertyPurpose
config_filesTells the plugin which configuration files to look for when running
exported_config_pathsTells Qlty to copy these files from the source repo to the child repo

Example in source.toml:

1# Enable the plugin and specify which config files it uses
2[[plugin]]
3name = "<plugin-name>"
4config_files = ["<config-file>"]
5mode = "monitor"
6
7# Export the config files so child repos receive them
8[plugins.definitions.<plugin-name>]
9exported_config_paths = ["<config-file>"]

You may place plugin configuration files anywhere within your custom source repo. The exported_config_paths must be relative to the location of your source.toml.

Sharing Linter Extensions

If a shared plugin configuration file utilizes linter extensions, you’ll need to define these using the extra_packages property within your source.toml. For example:

1[[plugin]]
2name = "eslint"
3version = "8.57.0"
4extra_packages = [
5 "eslint-config-prettier@10.1.8",
6 "eslint-plugin-prettier@5.5.3",
7 "prettier@3.6.2"
8]

Private Custom Sources

Custom sources can be private repos, provided they are in the same GitHub Org and the user has installed the Qlty GitHub App.

Qlty Cloud: HTTPS URLs work automatically. The GitHub App handles authentication.

1[[source]]
2name = "AcmeCo"
3repository = "https://github.com/AcmeCo/qlty-source"
4branch = "main"

Qlty CLI (local): For private repos, use SSH URLs since the CLI doesn’t have access to Qlty Cloud authentication:

1[[source]]
2name = "AcmeCo"
3repository = "git@github.com:AcmeCo/qlty-source.git"
4branch = "main"

Complete Example

Here’s a complete working example of sharing configuration between repositories.

Source Repository

source.toml:

1config_version = "0"
2
3[[source]]
4name = "default"
5default = true
6
7# Shared exclusions
8exclude_patterns = [
9 "**/tmp/**",
10 "**/vendor/**",
11]
12
13# Enable a plugin with shared config
14[[plugin]]
15name = "<plugin-name>"
16config_files = ["<config-file>"]
17mode = "monitor"
18
19# Export the config file to child repos
20[plugins.definitions.<plugin-name>]
21exported_config_paths = ["<config-file>"]

Child Repository

.qlty/qlty.toml:

1config_version = "0"
2
3[[source]]
4name = "default"
5default = true
6
7[[source]]
8name = "acme"
9repository = "https://github.com/AcmeCo/acme-qlty-source"
10branch = "main"

The child repository does not need its own linter configuration files — they will be inherited from the source repository when Qlty runs.