comply-all-rule-types-jsonunchanged
Asserts — The machine-readable shape of every rule type: fieldPath and actualValue on package-field hits, installedRange/requiredRange on require-engine-version, matchedFiles on codeowners. Also where #95 is visible — the two codeowners entries are byte-identical apart from matchedFiles.
Ran hermex comply --format json in fixtures/repos/all-rule-types → exit 1, as asserted
Config fixtures/repos/all-rule-types/hermex.config.ts · Fixture fixtures/repos/all-rule-types (overview) · Case comply-all-rule-types-json (dossier)
Reproduce locally: pnpm run test:output -- --filter comply-all-rule-types-json
fixtures/repos/all-rule-types/hermex.config.ts
import type { HermexConfigInput } from '../../../src/config/types.ts';
/**
* Every rule type hermex has, all firing at once, at three different
* severities. The primary fixture repo only ever trips three of the nine —
* so without this repo the rules table has never been reviewed with an
* `require-engine-version` row, a `require-codeowners` row, or either of the
* package-field shapes in it, and nothing would catch a renderer that
* mishandles `fieldPath` / `installedRange` / a long `matchedFiles` list.
*
* Scoped to `src/` so `jest.config.js` is found by `no-files` without
* also being parsed as source.
*/
export default {
includes: ['src/**/*.{tsx,jsx,ts,js}'],
rules: {
'no-files': [
{
severity: 'error',
patterns: ['jest.config.*', '.babelrc'],
message: 'Use vitest + Vite',
},
],
'require-files': [{ severity: 'error', patterns: ['.nvmrc'] }],
'no-packages': [
{ severity: 'error', patterns: ['moment'], message: 'Use date-fns or dayjs' },
],
'require-packages': [
{ severity: 'error', patterns: ['typescript'], message: 'TypeScript is required' },
],
'require-scripts': [
{ severity: 'error', patterns: ['build', 'test'], message: 'Required npm scripts' },
],
// Missing outright, so the violation reports the absence.
'require-package-fields': [{ severity: 'warn', patterns: ['license'] }],
// Present, so the violation reports the offending value — the other
// half of the package-field renderer.
'no-package-fields': [
{
severity: 'warn',
patterns: ['publishConfig.registry'],
message: 'Publish to the public registry',
},
],
// engines.node is ">=16", so this reports both ranges rather than the
// "not specified" shape.
'require-engine-version': { severity: 'error', range: '>=20', message: 'Minimum Node 20 required' },
// CODEOWNERS covers two of the three scanned files, and one of those
// belongs to a team outside `requiredOwners` — so this produces both
// codeowners violations, unowned and wrong-owner.
//
// The baseline currently describes both as "have no owner", which is
// wrong for src/legacy.tsx: it has an owner, just not a required one.
// That is #95, left unfixed on purpose — the recorded output is the
// evidence, and refreshing this baseline is how the fix gets reviewed.
'require-codeowners': {
severity: 'info',
requiredOwners: ['@org/platform'],
message: 'Every file needs a platform owner',
},
},
output: {
packages: false,
components: false,
patterns: false,
versus: false,
},
} satisfies HermexConfigInput;
stdout.json{
"version": "<version>",
"summary": {
"filesAnalyzed": 3,
"totalImports": 3,
"totalComponents": 1,
"totalUsagePatterns": 5,
"patternCounts": [
{
"patternType": "imports.named",
"displayName": "Named Imports",
"count": 3
},
{
"patternType": "usage.jsx",
"displayName": "JSX Usage",
"count": 1
},
{
"patternType": "imports.default",
"displayName": "Default Imports",
"count": 0
},
{
"patternType": "imports.namespace",
"displayName": "Namespace Imports",
"count": 0
},
{
"patternType": "imports.aliased",
"displayName": "Aliased Imports",
"count": 0
},
{
"patternType": "usage.variables",
"displayName": "Variable Assignments",
"count": 0
},
{
"patternType": "usage.destructuring",
"displayName": "Destructuring",
"count": 0
},
{
"patternType": "usage.conditional",
"displayName": "Conditional Usage",
"count": 0
},
{
"patternType": "usage.arrays",
"displayName": "Array Mappings",
"count": 0
},
{
"patternType": "usage.objects",
"displayName": "Object Mappings",
"count": 0
},
{
"patternType": "advanced.lazy",
"displayName": "Lazy Loading",
"count": 0
},
{
"patternType": "advanced.dynamic",
"displayName": "Dynamic Imports",
"count": 0
},
{
"patternType": "advanced.hoc",
"displayName": "Higher-Order Components",
"count": 0
},
{
"patternType": "advanced.memo",
"displayName": "Memoized Components",
"count": 0
},
{
"patternType": "advanced.forwardRef",
"displayName": "Forward Refs",
"count": 0
},
{
"patternType": "advanced.portal",
"displayName": "Portal Usage",
"count": 0
}
]
},
"packages": [
{
"packageName": "react",
"version": "18.3.1",
"rootVersion": "18.3.1",
"declaredIn": [
"dependencies"
],
"componentCount": 1,
"usageCount": 1,
"percentage": 100,
"internal": false,
"hasVersionConflict": false,
"allVersions": [
"18.3.1"
]
},
{
"packageName": "moment",
"version": "2.29.4",
"rootVersion": "2.29.4",
"declaredIn": [
"dependencies"
],
"componentCount": 0,
"usageCount": 0,
"percentage": 0,
"internal": false,
"hasVersionConflict": false,
"allVersions": [
"2.29.4"
]
}
],
"components": [
{
"name": "Fragment",
"source": "react",
"count": 1,
"files": [
"src/orphan.tsx"
]
}
],
"versus": [],
"ruleViolations": [
{
"ruleId": "no-packages",
"severity": "error",
"patterns": [
"moment"
],
"message": "Use date-fns or dayjs",
"matchedFiles": [],
"packageName": "moment"
},
{
"ruleId": "require-packages",
"severity": "error",
"patterns": [
"typescript"
],
"message": "TypeScript is required",
"matchedFiles": []
},
{
"ruleId": "no-files",
"severity": "error",
"patterns": [
"jest.config.*",
".babelrc"
],
"message": "Use vitest + Vite",
"matchedFiles": [
"jest.config.js",
".babelrc"
]
},
{
"ruleId": "require-files",
"severity": "error",
"patterns": [
".nvmrc"
],
"matchedFiles": []
},
{
"ruleId": "require-scripts",
"severity": "error",
"patterns": [
"build",
"test"
],
"message": "Required npm scripts",
"matchedFiles": []
},
{
"ruleId": "require-package-fields",
"severity": "warn",
"patterns": [
"license"
],
"matchedFiles": []
},
{
"ruleId": "no-package-fields",
"severity": "warn",
"patterns": [
"publishConfig.registry"
],
"message": "Publish to the public registry",
"matchedFiles": [],
"fieldPath": "publishConfig.registry",
"actualValue": "https://npm.internal.example.com"
},
{
"ruleId": "require-engine-version",
"severity": "error",
"patterns": [],
"message": "Minimum Node 20 required",
"matchedFiles": [],
"installedRange": ">=16",
"requiredRange": ">=20"
},
{
"ruleId": "require-codeowners",
"severity": "info",
"patterns": [
"CODEOWNERS"
],
"message": "Every file needs a platform owner",
"matchedFiles": [
"src/orphan.tsx"
]
},
{
"ruleId": "require-codeowners",
"severity": "info",
"patterns": [
"CODEOWNERS"
],
"message": "Every file needs a platform owner",
"matchedFiles": [
"src/legacy.tsx"
]
}
],
"compliance": {
"status": "non-compliant",
"compliant": false,
"counts": {
"errorRuleViolations": 6,
"releaseAgeViolations": 0,
"warningRuleViolations": 2
}
}
}
stderr.txthermex v<version>
- Parsing lockfile...
✔ Found pnpm lockfile (supports: v5, v6, v9) - 2 packages
✔ Found 3 files
✔ Analysis complete! Analyzed 3/3 files