Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/pnpm-resolve-pinned/action.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
description: Resolve a package's locked version from pnpm-lock.yaml (no install required)
inputs:
package:
Expand Down
1 change: 1 addition & 0 deletions .github/actions/pnpm-tasks/action.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
description: Set up pnpm and Node.js, restore cache, and install dependencies
inputs:
commands:
Expand Down
1 change: 1 addition & 0 deletions .github/actions/turbo-run/action.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
description: Run a turbo task, skipping pnpm install on full cache hit
inputs:
task:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/changeset-check.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pre-commit-seed.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
# Pin prek cache to a known path for reliable GitHub Actions caching
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
# Pin prek cache to a known path for reliable GitHub Actions caching
Expand Down
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ packages/
cli/ — @gtbuchanan/cli (gtb build CLI for consumers)
eslint-config/ — @gtbuchanan/eslint-config (ESLint configure())
eslint-plugin-markdownlint/ — @gtbuchanan/eslint-plugin-markdownlint (markdownlint via ESLint)
eslint-plugin-yamllint/ — @gtbuchanan/eslint-plugin-yamllint (yamllint gap rules via ESLint)
tsconfig/ — @gtbuchanan/tsconfig (shared base tsconfig.json)
vitest-config/ — @gtbuchanan/vitest-config (configurePackage, configureGlobal, + e2e variants)
test-utils/ — private shared E2E fixture utilities
Expand Down Expand Up @@ -117,6 +118,8 @@ globs for monorepos, or falls back to single-package mode.
`eslint-plugin-import-x` (ordering), `@eslint/json` (JSON linting),
`eslint-plugin-pnpm` (workspace validation), `eslint-plugin-n` (Node.js
rules), `eslint-plugin-yml` (YAML linting + key sorting),
`eslint-plugin-yamllint` (yamllint gap rules: truthy, octal-values,
anchors, document-start/end),
`eslint-plugin-markdownlint` (Markdown structural linting),
`@vitest/eslint-plugin` (test rules), and `eslint-plugin-only-warn`
(downgrades errors to warnings).
Expand Down Expand Up @@ -261,6 +264,9 @@ Consumer guidance:
`<!-- markdownlint-disable-next-line MD024 -->`. This keeps the
plugin compatible with standalone markdownlint usage.
- All exported functions, types, interfaces, and constants must have JSDoc comments.
- When adding or removing a package, update the packages table in
`README.md`, the structure tree above, and the linter/formatter
sections as applicable.
- When asserting on `CommandResult` (exit code, stdout, stderr), use
`expect(result).toMatchObject({ exitCode: 0 })` instead of
`expect(result.exitCode).toBe(0)`. On failure, `toMatchObject` shows
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Shared build configuration monorepo for JavaScript/TypeScript projects.
| [@gtbuchanan/cli](packages/cli) | Shared build CLI (`gtb`) |
| [@gtbuchanan/eslint-config](packages/eslint-config) | Shared ESLint configuration |
| [@gtbuchanan/eslint-plugin-markdownlint](packages/eslint-plugin-markdownlint) | ESLint plugin wrapping markdownlint |
| [@gtbuchanan/eslint-plugin-yamllint](packages/eslint-plugin-yamllint) | ESLint plugin for yamllint gap rules |
| [@gtbuchanan/tsconfig](packages/tsconfig) | Shared TypeScript base configuration |
| [@gtbuchanan/vitest-config](packages/vitest-config) | Shared Vitest configuration |

Expand Down
9 changes: 9 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
codecov:
require_ci_to_pass: true
comment:
Expand All @@ -18,6 +19,10 @@ component_management:
name: test-utils
paths:
- packages/test-utils/src/**
- component_id: eslint-plugin-yamllint
name: eslint-plugin-yamllint
paths:
- packages/eslint-plugin-yamllint/src/**
- component_id: eslint-plugin-markdownlint
name: eslint-plugin-markdownlint
paths:
Expand Down Expand Up @@ -53,6 +58,10 @@ flags:
carryforward: true
paths:
- packages/eslint-plugin-markdownlint/
eslint-plugin-yamllint:
carryforward: true
paths:
- packages/eslint-plugin-yamllint/
test-utils:
carryforward: true
paths:
Expand Down
19 changes: 16 additions & 3 deletions packages/eslint-config/e2e/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ const createFixture = () => {
depsPackages: ['typescript'],
hookPackages: ['eslint', 'jiti'],
packageName: '@gtbuchanan/eslint-config',
workspaceDeps: ['@gtbuchanan/eslint-plugin-markdownlint'],
workspaceDeps: [
'@gtbuchanan/eslint-plugin-markdownlint',
'@gtbuchanan/eslint-plugin-yamllint',
],
});

const eslint = path.join(fixture.hookDir, 'node_modules/.bin/eslint');
Expand Down Expand Up @@ -222,7 +225,7 @@ describe.concurrent('eslint CLI integration', () => {
it('formats JSON, Markdown, YAML, and CSS via Prettier plugins', async ({ fixture, expect }) => {
const unsortedJson = '{\n "z": 1,\n "a": [1, 2]\n}\n';
const longMarkdown = `# Title\n\n${'word '.repeat(40).trim()}\n`;
const doubleQuotedYaml = 'key: "value"\n';
const doubleQuotedYaml = '---\nkey: "value"\n';
const unsortedCss = '.box {\n display: flex;\n color: red;\n}\n';

const result = await fixture.run({
Expand All @@ -246,14 +249,24 @@ describe.concurrent('eslint CLI integration', () => {
expect(result.readFile('doc.md')).toBe(longMarkdown);

// YAML: singleQuote from prettierDefaults converts double quotes
expect(result.readFile('config.yml')).toBe("key: 'value'\n");
expect(result.readFile('config.yml')).toBe("---\nkey: 'value'\n");

// CSS: alphabetical property sorting (color before display)
expect(result.readFile('style.css')).toBe(
'.box {\n color: red;\n display: flex;\n}\n',
);
});

it('detects yamllint violations in YAML files', async ({ fixture, expect }) => {
const yaml = '---\ncountry: NO\nperms: 0777\n';
const result = await fixture.run({
files: { 'config.yml': yaml },
});

expect(result.stdout).toContain('yamllint/truthy');
expect(result.stdout).toContain('yamllint/octal-values');
});

it('detects markdownlint violations in markdown files', async ({ fixture, expect }) => {
const result = await fixture.run({
files: { 'doc.md': '# Title\n\n# Title\n' },
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@eslint-community/eslint-plugin-eslint-comments": "catalog:",
"@eslint/json": "catalog:",
"@gtbuchanan/eslint-plugin-markdownlint": "workspace:*",
"@gtbuchanan/eslint-plugin-yamllint": "workspace:*",
"@prettier/plugin-xml": "catalog:",
"@stylistic/eslint-plugin": "catalog:",
"@vitest/eslint-plugin": "catalog:",
Expand Down
3 changes: 2 additions & 1 deletion packages/eslint-config/src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import typescript from './typescript.ts';
import unicorn from './unicorn.ts';
import vitest from './vitest.ts';
import yaml from './yaml.ts';
import yamllint from './yamllint.ts';

/** Ordered plugin factories. Later entries override earlier ones for the same file. */
export const plugins: readonly PluginFactory[] = [
typescript, unicorn, promise, regexp, jsdoc, json, yaml, pnpm, node,
typescript, unicorn, promise, regexp, jsdoc, json, yaml, yamllint, pnpm, node,
format, markdownlint, stylistic, eslintComments, importX,
core, vitest,
];
12 changes: 9 additions & 3 deletions packages/eslint-config/src/plugins/yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ import type { PluginFactory } from '../index.ts';

// --- YAML ---

/** YAML linting configs with key sorting. */
/** YAML linting configs with key sorting and stricter value rules. */
const plugin: PluginFactory = () => [
...ymlConfigs['flat/recommended'],
...ymlConfigs['flat/prettier'],
{
files: ['**/*.yaml', '**/*.yml'],
// Justification: Alphabetical keys reduce merge conflicts in shared YAML configs
rules: { 'yml/sort-keys': 'warn' },
rules: {
// Justification: Trailing zeros obscure numeric precision intent (e.g., 1.0 vs 1)
'yml/no-trailing-zeros': 'warn',
// Justification: Non-string keys (numbers, booleans) cause subtle type coercion bugs
'yml/require-string-key': 'warn',
// Justification: Alphabetical keys reduce merge conflicts in shared YAML configs
'yml/sort-keys': 'warn',
},
},
];

Expand Down
31 changes: 31 additions & 0 deletions packages/eslint-config/src/plugins/yamllint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import yamllint from '@gtbuchanan/eslint-plugin-yamllint';
import type { PluginFactory } from '../index.ts';

/** yamllint-equivalent YAML rules via `@gtbuchanan/eslint-plugin-yamllint`. */
const plugin: PluginFactory = () => [
{
files: ['**/*.yaml', '**/*.yml'],
plugins: { yamllint },
rules: {
'yamllint/anchors': 'warn',
'yamllint/document-end': 'warn',
'yamllint/document-start': 'warn',
'yamllint/octal-values': 'warn',
'yamllint/truthy': ['warn', {
'allowed-values': ['true', 'false'],
}],
},
},
{
/*
* Renovate strips document start markers from pnpm-workspace.yaml
* when bumping dependencies. Disable the rule to avoid noise.
*/
files: ['pnpm-workspace.yaml'],
rules: {
'yamllint/document-start': 'off',
},
},
];

export default plugin;
116 changes: 116 additions & 0 deletions packages/eslint-plugin-yamllint/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# @gtbuchanan/eslint-plugin-yamllint

ESLint plugin implementing [yamllint](https://github.com/adrienverge/yamllint)-equivalent
rules as native ESLint rules using the [yaml](https://eemeli.org/yaml/) npm
package. No Python dependency required.

## Why?

[Prettier](https://prettier.io/) and
[eslint-plugin-yml](https://ota-meshi.github.io/eslint-plugin-yml/) cover most
of yamllint's rules, but several high-impact rules have no equivalent. This
plugin fills those gaps.

### yamllint rule coverage

| yamllint rule | Covered by | Notes |
| ----------------------- | -------------------------------------- | --------------------------------------------------------------- |
| anchors | **eslint-plugin-yamllint** | Unused/duplicate anchors, undeclared aliases |
| braces | eslint-plugin-yml + Prettier | |
| brackets | eslint-plugin-yml + Prettier | |
| colons | eslint-plugin-yml + Prettier | |
| commas | Prettier | |
| comments | eslint-plugin-yml (partial) | `min-spaces-from-content` not covered |
| comments-indentation | _not covered_ | |
| document-end | **eslint-plugin-yamllint** | Require or forbid `...` markers |
| document-start | **eslint-plugin-yamllint** | Require or forbid `---` markers |
| empty-lines | eslint-plugin-yml + Prettier | |
| empty-values | eslint-plugin-yml | |
| float-values | eslint-plugin-yml (partial) | Only trailing zeros; no leading-dot/scientific/NaN/Inf |
| hyphens | Prettier | |
| indentation | eslint-plugin-yml + Prettier | |
| key-duplicates | YAML parser | Hard parse error, not configurable |
| key-ordering | eslint-plugin-yml | `yml/sort-keys` is a superset |
| line-length | Prettier (partial) | `printWidth` is a soft target, not a hard limit |
| new-line-at-end-of-file | Prettier | |
| new-lines | Prettier | |
| octal-values | **eslint-plugin-yamllint** | Implicit (`0777`) and explicit (`0o777`) octals |
| quoted-strings | eslint-plugin-yml + Prettier (partial) | Basic style only; `required`/`extra-*`/`check-keys` not covered |
| trailing-spaces | Prettier | |
| truthy | **eslint-plugin-yamllint** | Unquoted YAML 1.1 boolean-like values (auto-fixable) |

## Install

```sh
pnpm add -D @gtbuchanan/eslint-plugin-yamllint eslint
```

## Usage

```typescript
// eslint.config.ts
import yamllint from '@gtbuchanan/eslint-plugin-yamllint';

export default [
{
files: ['**/*.yaml', '**/*.yml'],
plugins: { yamllint },
rules: {
'yamllint/anchors': 'warn',
'yamllint/document-end': 'warn',
'yamllint/document-start': 'warn',
'yamllint/octal-values': 'warn',
'yamllint/truthy': ['warn', { 'allowed-values': ['true', 'false'] }],
},
},
];
```

## Rules

### `yamllint/truthy`

Flags unquoted YAML 1.1 boolean-like values (`yes`, `no`, `on`, `off`, `y`,
`n`, `true`, `false`, and case variants) that may be silently coerced.
Auto-fixable — wraps values in double quotes.

| Option | Type | Default | Description |
| ---------------- | ---------- | ------- | ------------------------ |
| `allowed-values` | `string[]` | `[]` | Values to allow unquoted |
| `check-keys` | `boolean` | `false` | Also check mapping keys |

### `yamllint/octal-values`

Flags implicit (`0777`) and explicit (`0o777`) YAML 1.1 octal literals that
may be interpreted differently across YAML versions.

| Option | Type | Default | Description |
| ----------------------- | --------- | ------- | ------------------------- |
| `forbid-implicit-octal` | `boolean` | `true` | Flag `0777`-style octals |
| `forbid-explicit-octal` | `boolean` | `true` | Flag `0o777`-style octals |

### `yamllint/anchors`

Detects unused anchors, duplicate anchors, and undeclared aliases.

| Option | Type | Default | Description |
| --------------------------- | --------- | ------- | ------------------------------------ |
| `forbid-duplicated-anchors` | `boolean` | `true` | Flag duplicate `&name` anchors |
| `forbid-undeclared-aliases` | `boolean` | `true` | Flag `*name` without matching anchor |
| `forbid-unused-anchors` | `boolean` | `true` | Flag anchors with no alias reference |

### `yamllint/document-start`

Requires or forbids `---` document start markers. Auto-fixable.

| Option | Type | Default | Description |
| --------- | --------- | ------- | ----------------------------------------------- |
| `present` | `boolean` | `true` | Require (`true`) or forbid (`false`) the marker |

### `yamllint/document-end`

Requires or forbids `...` document end markers. Auto-fixable.

| Option | Type | Default | Description |
| --------- | --------- | ------- | ----------------------------------------------- |
| `present` | `boolean` | `false` | Require (`true`) or forbid (`false`) the marker |
5 changes: 5 additions & 0 deletions packages/eslint-plugin-yamllint/eslint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { configure } from '../eslint-config/src/index.ts';

export default configure({
tsconfigRootDir: import.meta.dirname,
});
42 changes: 42 additions & 0 deletions packages/eslint-plugin-yamllint/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@gtbuchanan/eslint-plugin-yamllint",
"version": "0.1.0",
"description": "ESLint plugin implementing yamllint-equivalent rules for YAML files",
"type": "module",
"imports": {
"#src/*": "./src/*"
},
"exports": {
".": "./src/index.ts"
},
"scripts": {
"compile:ts": "pnpm run gtb compile:ts",
"coverage:codecov:upload": "pnpm run gtb coverage:codecov:upload",
"coverage:vitest:merge": "pnpm run gtb coverage:vitest:merge",
"gtb": "node --experimental-strip-types ../../packages/cli/bin/gtb.ts",
"lint:eslint": "pnpm run gtb lint:eslint",
"pack:npm": "pnpm run gtb pack:npm",
"test:vitest:fast": "pnpm run gtb test:vitest:fast",
"test:vitest:slow": "pnpm run gtb test:vitest:slow",
"typecheck:ts": "pnpm run gtb typecheck:ts"
},
"dependencies": {
"yaml": "catalog:"
},
"devDependencies": {
"@gtbuchanan/vitest-config": "workspace:*"
},
"peerDependencies": {
"eslint": "^10.0.0"
},
"engines": {
"node": ">=22.17"
},
"publishConfig": {
"directory": "dist/source",
"exports": {
".": "./src/index.js"
},
"linkDirectory": false
}
}
Loading
Loading