Hermex Output Review

← all cases

comply-all-rule-types-json

changed

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

Config

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;

Diff against the target branch

Diffs are unified format: - is the target branch, + is this run. @@ -12,7 +12,9 @@ is a hunk header — unchanged lines were skipped, and the hunk below covers 7 lines from line 12 of the target branch and 9 lines from line 12 of this run.

--- target/stdout.json
+++ current/stdout.json
@@ -4,135 +4,8 @@
     "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
-      }
-    ]
+    "totalUsagePatterns": 5
   },
-  "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",

Full output

stdout.json
{
  "version": "<version>",
  "summary": {
    "filesAnalyzed": 3,
    "totalImports": 3,
    "totalComponents": 1,
    "totalUsagePatterns": 5
  },
  "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.txt
hermex v<version>
- Parsing lockfile...
✔ Found pnpm lockfile (supports: v5, v6, v9) - 2 packages
✔ Found 3 files
✔ Analysis complete! Analyzed 3/3 files