repofmt
v0.3.0
Published
A CLI tool that checks consistency across repositories
Maintainers
Readme
repofmt
This tool checks consistency across repositories.
Usage
GitHub Action
See repofmt-action for more details on how to use the GitHub action.
CLI
npx repofmt run [options]Options
| Option | Description | Required | Default |
|--------|-------------|----------|---------|
| --config <path> | Path to the config file. | No | repofmt.config.ts |
| --token <token> | GitHub token (can also use GITHUB_TOKEN env var). | No | |
Configuration
Inputs
github-token
github-token is the GitHub token to use for API access. It is optional and must be a personal access token with the repo scope. If not provided, the action will use the default GitHub token.
repofmt.config.ts
The action can be configured by creating a repofmt.config.ts file at the root of your repository. repofmt.config.js, repofmt.config.mjs, and repofmt.config.cjs are also supported.
export default {
filters: {
archived: false,
organizations: ['my-org', 'other-org'],
visibility: 'public',
include: ['^my-org-', '^other-org-'],
exclude: ['^deprecated-'],
},
rules: [
{
name: 'file-exists',
level: 'error',
options: {
caseSensitive: true,
path: 'file.md',
},
},
],
};Filters
The filters option allows you to control which repositories are checked by the action.
{
filters: {
archived: false,
organizations: ['my-org', 'other-org'],
visibility: 'public',
include: ['^my-org-', '^other-org-'],
exclude: ['^deprecated-'],
}
}| Option | Description | Required | Default |
|------------------|-----------------------------------------------------------------|----------|--------------|
| archived | Filter repositories by archived status: true (only archived), false (only not archived), or undefined (all). | No | undefined |
| organizations | Array of organization names to include. A repository must belong to one of the specified organizations to be included. | No | |
| visibility | Filter repositories by visibility: 'public', 'private', or 'all'. | No | 'all' |
| include | Array of regex patterns to match repository names (matches both name and full_name). A repository is included if it matches any pattern in the array. | No | |
| exclude | Array of regex patterns to exclude repository names (matches both name and full_name). A repository is excluded if it matches any pattern in the array. | No | |
- A repository must pass all specified filters to be included.
Rules
Each rule has the following structure:
{
name: 'rule-name',
level: 'error' | 'warning',
options: { /* rule-specific options */ },
exceptions?: string[]
}Common Rule Fields
| Field | Description | Required | Default |
|-------------|-----------------------------------------------------------------|----------|--------------|
| name | The name of the rule to apply. | Yes | |
| level | The alert level: 'error' or 'warning'. | Yes | |
| options | Rule-specific configuration options. | Yes | |
| exceptions| Array of regex patterns to exclude repositories from this rule. A repository is excluded if its name or full_name matches any pattern in the array. | No | |
Exceptions: The exceptions field allows you to bypass a specific rule for certain repositories. This is useful when you want a rule to apply to most repositories but need to exclude specific ones. Exceptions are checked per-rule, so you can have different exceptions for different rules.
Example: Skip a rule for legacy repositories:
{
name: 'file-exists',
level: 'error',
options: {
path: 'README.md',
},
exceptions: ['^legacy-', '^deprecated-'],
}file-contains
The file-contains rule checks if a file contains a specific string.
{
"name": "file-contains",
"level": "error",
"options": {
"path": "README.md",
"contains": "MIT License",
"caseSensitive": false
}
}| Option | Description | Required | Default |
|-----------------|-----------------------------------------------------------------|----------|--------------|
| path | The path to the file(s) to check, or an array of paths/glob patterns. | Yes | |
| contains | The string that the file(s) must contain. | Yes | |
| caseSensitive | Whether the string matching should be case-sensitive. | No | false |
The path option supports:
- Single file paths (e.g.,
README.md,.github/workflows/ci.yml) - Glob patterns (e.g.,
**/*.md,src/**/*.ts) - Arrays of paths/patterns (e.g.,
['README.md', 'LICENSE'],['**/*.md', '**/*.txt'])
When using glob patterns or arrays, all matching files must contain the specified string.
Example: Check if a README contains a license notice:
{
"name": "file-contains",
"level": "error",
"options": {
"path": "README.md",
"contains": "MIT License"
}
}Example: Check if all markdown files contain a license notice:
{
"name": "file-contains",
"level": "error",
"options": {
"path": "**/*.md",
"contains": "MIT License"
}
}Example: Check multiple specific files:
{
"name": "file-contains",
"level": "error",
"options": {
"path": ["README.md", "LICENSE"],
"contains": "MIT"
}
}file-exists
The file-exists rule checks if a file exists in the repository.
{
"name": "file-exists",
"level": "error",
"options": {
"caseSensitive": true,
"path": "file.md"
}
}| Option | Description | Required | Default |
|-----------------|-----------------------------------------------------------------|----------|--------------|
| caseSensitive | Whether to check if the file exists in a case-sensitive manner. | No | false |
| path | The path to the file, or an array of alternative paths. | Yes | |
| type | The type of entry to look for: file, directory, or any. | No | file |
The path option supports nested paths (e.g., .github/workflows/ci.yml).
You can also provide an array of alternative paths. The rule passes if at least one of the entries exists:
{
"name": "file-exists",
"level": "error",
"options": {
"path": ["config.yml", "config.yaml"]
}
}You can use the type option to check for directories instead of files:
{
"name": "file-exists",
"level": "error",
"options": {
"path": "src",
"type": "directory"
}
}file-forbidden
The file-forbidden rule checks that a file does NOT exist in the repository. This is useful for enforcing that certain files (like .DS_Store, .env, etc.) are not committed.
{
"name": "file-forbidden",
"level": "error",
"options": {
"caseSensitive": true,
"path": ".DS_Store"
}
}| Option | Description | Required | Default |
|-----------------|-----------------------------------------------------------------|----------|--------------|
| caseSensitive | Whether to check if the file exists in a case-sensitive manner. | No | false |
| path | The path to the file(s) that should not exist. | Yes | |
| type | The type of entry to look for: file, directory, or any. | No | file |
The path option supports nested paths (e.g., src/config/secrets.json).
You can provide an array of paths to forbid multiple files:
{
"name": "file-forbidden",
"level": "error",
"options": {
"path": [".env", ".env.local", ".env.production"]
}
}You can use the type option to forbid directories instead of files:
{
"name": "file-forbidden",
"level": "error",
"options": {
"path": "node_modules",
"type": "directory"
}
}file-not-contains
The file-not-contains rule checks that a file does NOT contain a specific string. This is useful for enforcing that certain content (like secrets, deprecated code, etc.) is not present in files. If you use this rule to look for secrets, do NOT enter a specific secret (as you don't want your committed config to contain a secret), but instead use common secret patterns, such as the "sk_" prefix for example..
{
"name": "file-not-contains",
"level": "error",
"options": {
"path": "README.md",
"contains": "SECRET",
"caseSensitive": false
}
}| Option | Description | Required | Default |
|-----------------|-----------------------------------------------------------------|----------|--------------|
| path | The path to the file(s) to check, or an array of paths/glob patterns. | Yes | |
| contains | The string that the file(s) must NOT contain. | Yes | |
| caseSensitive | Whether the string matching should be case-sensitive. | No | false |
The path option supports:
- Single file paths (e.g.,
README.md,.github/workflows/ci.yml) - Glob patterns (e.g.,
**/*.md,src/**/*.ts) - Arrays of paths/patterns (e.g.,
['README.md', 'LICENSE'],['**/*.md', '**/*.txt'])
When using glob patterns or arrays, all matching files must not contain the specified string.
Example: Check that a README does not contain a secret:
{
"name": "file-not-contains",
"level": "error",
"options": {
"path": "README.md",
"contains": "SECRET_KEY"
}
}Example: Check that all markdown files do not contain deprecated content:
{
"name": "file-not-contains",
"level": "error",
"options": {
"path": "**/*.md",
"contains": "DEPRECATED"
}
}Example: Check multiple specific files:
{
"name": "file-not-contains",
"level": "error",
"options": {
"path": [".env", ".env.local"],
"contains": "password"
}
}github-actions/timeout-minutes
The github-actions/timeout-minutes rule checks if the GitHub Actions timeout is set.
{
"name": "github-actions/timeout-minutes",
"level": "error",
"options": {
"maximum": 30
}
}| Option | Description | Required | Default |
|------------------|-----------------------------------------------------------------|----------|--------------|
| maximum | Maximum allowed timeout value in minutes. If provided, the rule will check that all timeout values are lower than this maximum. | No | |
json-has-keys
The json-has-keys rule checks if all specified keys are defined in JSON files. Keys can be nested using dot notation (e.g., parent.child.grandchild).
{
"name": "json-has-keys",
"level": "error",
"options": {
"path": "package.json",
"keys": ["name", "version", "scripts.build"]
}
}| Option | Description | Required | Default |
|-----------------|-----------------------------------------------------------------|----------|--------------|
| path | The path to the JSON file(s) to check, or an array of paths/glob patterns. | Yes | |
| keys | Array of keys to check. Use dot notation for nested keys (e.g., "parent.child"). | Yes | |
The path option supports:
- Single file paths (e.g.,
package.json,tsconfig.json) - Glob patterns (e.g.,
**/*.json,config/*.json) - Arrays of paths/patterns (e.g.,
['package.json', 'package-lock.json'])
When using glob patterns or arrays, all matching files must contain all specified keys.
Example: Check that package.json has required fields:
{
"name": "json-has-keys",
"level": "error",
"options": {
"path": "package.json",
"keys": ["name", "version", "description"]
}
}Example: Check nested keys:
{
"name": "json-has-keys",
"level": "error",
"options": {
"path": "tsconfig.json",
"keys": ["compilerOptions.target", "compilerOptions.module"]
}
}Example: Check multiple JSON files:
{
"name": "json-has-keys",
"level": "error",
"options": {
"path": ["package.json", "package-lock.json"],
"keys": ["name", "version"]
}
}Example: Check all JSON files in a directory:
{
"name": "json-has-keys",
"level": "error",
"options": {
"path": "config/**/*.json",
"keys": ["name"]
}
}yaml-has-keys
The yaml-has-keys rule checks if all specified keys are defined in YAML files. Keys can be nested using dot notation (e.g., parent.child.grandchild).
{
"name": "yaml-has-keys",
"level": "error",
"options": {
"path": "config.yml",
"keys": ["name", "version", "scripts.build"]
}
}| Option | Description | Required | Default |
|-----------------|-----------------------------------------------------------------|----------|--------------|
| path | The path to the YAML file(s) to check, or an array of paths/glob patterns. | Yes | |
| keys | Array of keys to check. Use dot notation for nested keys (e.g., "parent.child"). | Yes | |
The path option supports:
- Single file paths (e.g.,
config.yml,.github/workflows/ci.yml) - Glob patterns (e.g.,
**/*.yml,**/*.yaml,config/*.yaml) - Arrays of paths/patterns (e.g.,
['config.yml', 'app.yaml'])
When using glob patterns or arrays, all matching files must contain all specified keys.
Example: Check that a config file has required fields:
{
"name": "yaml-has-keys",
"level": "error",
"options": {
"path": "config.yml",
"keys": ["name", "version", "description"]
}
}Example: Check nested keys:
{
"name": "yaml-has-keys",
"level": "error",
"options": {
"path": ".github/workflows/ci.yml",
"keys": ["on.push.branches", "jobs.build.steps"]
}
}Example: Check multiple YAML files:
{
"name": "yaml-has-keys",
"level": "error",
"options": {
"path": ["config.yml", "app.yaml"],
"keys": ["name", "version"]
}
}Example: Check all YAML files in a directory:
{
"name": "yaml-has-keys",
"level": "error",
"options": {
"path": "config/**/*.{yml,yaml}",
"keys": ["name"]
}
}license/exists
The license/exists rule checks if a LICENSE file exists in the repository.
{
"name": "license/exists",
"level": "error",
"options": {
"caseSensitive": true,
"path": "LICENSE.md"
}
}| Option | Description | Required | Default |
|-----------------|-----------------------------------------------------------------|----------|--------------|
| caseSensitive | Whether to check if the file exists in a case-sensitive manner. | No | false |
| path | The path to the file, or an array of alternative paths. | No | LICENSE.md |
You can provide an array of alternative paths:
{
"name": "license/exists",
"level": "error",
"options": {
"path": ["LICENSE", "LICENSE.md", "LICENSE.txt"]
}
}python/pyproject-dependencies-alphabetical-order
The python/pyproject-dependencies-alphabetical-order rule checks if dependencies in pyproject.toml are in alphabetical order.
{
"name": "python/pyproject-dependencies-alphabetical-order",
"level": "error",
"options": {
"path": "pyproject.toml",
"sections": ["project.dependencies", "project.optional-dependencies", "tool.poetry.dependencies"]
}
}| Option | Description | Required | Default |
|-----------------|-----------------------------------------------------------------|----------|--------------|
| path | The path to the pyproject.toml file. | No | pyproject.toml |
| sections | Array of dependency sections to check. Supported sections: project.dependencies, project.optional-dependencies, tool.poetry.dependencies. | No | ["project.dependencies", "project.optional-dependencies", "tool.poetry.dependencies"] |
The rule checks that dependencies are sorted alphabetically by package name (ignoring version specifiers and extras). For project.optional-dependencies, it checks each optional dependency group separately.
Example: Check if dependencies are in alphabetical order:
{
"name": "python/pyproject-dependencies-alphabetical-order",
"level": "error",
"options": {}
}Example: Check only Poetry dependencies:
{
"name": "python/pyproject-dependencies-alphabetical-order",
"level": "error",
"options": {
"sections": ["tool.poetry.dependencies"]
}
}python/requirements-txt-dependencies-alphabetical-order
The python/requirements-txt-dependencies-alphabetical-order rule checks if dependencies in requirements.txt are in alphabetical order.
{
"name": "python/requirements-txt-dependencies-alphabetical-order",
"level": "error",
"options": {
"path": "requirements.txt"
}
}| Option | Description | Required | Default |
|-----------------|-----------------------------------------------------------------|----------|--------------|
| path | The path to the requirements.txt file. | No | requirements.txt |
The rule checks that dependencies are sorted alphabetically by package name (ignoring version specifiers, extras, and comments). It handles:
- Standard packages:
package,package==1.0.0,package>=1.0.0,package[extra]>=1.0.0 - Editable installs:
-e .,-e ./local-package - Git URLs:
git+https://github.com/user/repo.git,git+https://github.com/user/repo.git@branch#egg=package-name - File paths:
./local-package,/path/to/package - Comments and empty lines are ignored
Example: Check if dependencies are in alphabetical order:
{
"name": "python/requirements-txt-dependencies-alphabetical-order",
"level": "error",
"options": {}
}Example: Check a custom requirements file:
{
"name": "python/requirements-txt-dependencies-alphabetical-order",
"level": "error",
"options": {
"path": "requirements-dev.txt"
}
}readme/exists
The readme/exists rule checks if a README file exists in the repository.
{
"name": "readme/exists",
"level": "error",
"options": {
"caseSensitive": true,
"path": "README.md"
}
}| Option | Description | Required | Default |
|-----------------|-----------------------------------------------------------------|----------|-------------|
| caseSensitive | Whether to check if the file exists in a case-sensitive manner. | No | false |
| path | The path to the file, or an array of alternative paths. | No | README.md |
You can provide an array of alternative paths:
{
"name": "readme/exists",
"level": "error",
"options": {
"path": ["README.md", "README.rst", "README.txt"]
}
}readme/has-badges
The readme/has-badges rule checks if a README file contains markdown badges (e.g., [](...)).
{
"name": "readme/has-badges",
"level": "error",
"options": {
"path": "README.md",
"minCount": 3,
"patterns": ["CI", "Coverage"]
}
}| Option | Description | Required | Default |
|-----------|--------------------------------------------------------------------------------------------------|----------|-------------|
| path | The path to the README file. | No | README.md |
| minCount | The minimum number of badges required. If not specified, at least one badge is required. | No | |
| patterns | Array of regex patterns to match against badge alt text, image URLs, or link URLs. A badge must be found for all patterns. | No | |
Examples:
Check that README has at least one badge:
{
"name": "readme/has-badges",
"level": "error"
}Check that README has at least 3 badges:
{
"name": "readme/has-badges",
"level": "error",
"options": {
"minCount": 3
}
}Check that README has specific badges (CI and Coverage):
{
"name": "readme/has-badges",
"level": "error",
"options": {
"patterns": ["CI", "Coverage"]
}
}Check that README has at least 2 badges and includes a CI badge:
{
"name": "readme/has-badges",
"level": "error",
"options": {
"minCount": 2,
"patterns": ["CI"]
}
}Patterns are matched case-insensitively against badge alt text, image URLs, and link URLs. For example, the pattern "CI" will match badges with alt text like "CI", "ci", or URLs containing "CI" or "ci".
readme/has-section
The readme/has-section rule checks if a README file contains a specific markdown section header (e.g., ## Usage, ## Installation).
{
"name": "readme/has-section",
"level": "error",
"options": {
"section": "Usage",
"path": "README.md",
"caseSensitive": false
}
}| Option | Description | Required | Default |
|-----------------|------------------------------------------------------------------------------|----------|-------------|
| section | The name of the section to check for (without the ## prefix). | Yes | |
| path | The path to the README file. | No | README.md |
| caseSensitive | Whether to check the section header in a case-sensitive manner. | No | false |
Example:
Check that README has a "Usage" section:
{
"name": "readme/has-section",
"level": "error",
"options": {
"section": "Usage",
"caseSensitive": true
}
}Note: This rule uses substring matching, so a section named "Usage Examples" will match a search for "Usage". For exact matching, specify the full section name.
