feat(core-eslint): rules E1 (no handler re-exports) and J (no direct payload.jobs)

Implements spec § 7 via no-restricted-syntax built-in selectors — mirrors
the R40 no-restricted-imports precedent; no custom plugin required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-08 12:18:14 +02:00
parent 1357e45f55
commit 5b766242be

View File

@@ -103,4 +103,66 @@ export default [
"no-restricted-imports": "off",
},
},
// E1 — Event handlers must not be re-exported. Wire them only inside the
// consumer feature's bind-production / bind-dev-seed (spec § 2.2 Rule E1).
// J — Direct `payload.jobs.*` access is forbidden outside the integration
// layer. Use IJobQueue (from @repo/core-shared/jobs) instead.
{
files: ["**/*.{ts,tsx,mjs,cjs,js}"],
rules: {
"no-restricted-syntax": [
"error",
{
selector:
"ExportNamedDeclaration[source.value=/\\/events\\/handlers\\//]",
message:
"Event handlers (events/handlers/*.handler.ts) must not be re-exported. Wire them only inside the consumer feature's bind-production / bind-dev-seed (Rule E1).",
},
{
selector:
"ExportAllDeclaration[source.value=/\\/events\\/handlers\\//]",
message:
"Event handlers (events/handlers/*.handler.ts) must not be re-exported. Wire them only inside the consumer feature's bind-production / bind-dev-seed (Rule E1).",
},
{
selector:
"MemberExpression[object.type='MemberExpression'][object.object.type='Identifier'][object.object.name='payload'][object.property.type='Identifier'][object.property.name='jobs']",
message:
"Direct `payload.jobs.*` access is not allowed here. Use IJobQueue (from @repo/core-shared/jobs) instead. Allowed only in **/integrations/cms/jobs/** and **/core-shared/src/jobs/**.",
},
],
},
},
// J — `payload.jobs.*` is allowed only in the integration layer.
// In these paths, no-restricted-syntax is narrowed to keep E1 active but
// drop the payload.jobs check.
// Note: "**/core-shared/src/jobs/**" does not match from within a package-local
// ESLint run because ESLint resolves globs relative to the config file location.
// The pattern is kept for documentation; in practice, the PayloadJobQueue class
// uses `this.payload.jobs.*` which the selector already ignores (it only catches
// bare `payload.jobs.*`). Any new file added there that does use bare `payload.jobs.*`
// would need this allowlist to be expressed as "**/jobs/payload-*" or similar.
{
files: [
"**/integrations/cms/jobs/**",
"**/core-shared/src/jobs/**",
],
rules: {
"no-restricted-syntax": [
"error",
{
selector:
"ExportNamedDeclaration[source.value=/\\/events\\/handlers\\//]",
message:
"Event handlers (events/handlers/*.handler.ts) must not be re-exported. Wire them only inside the consumer feature's bind-production / bind-dev-seed (Rule E1).",
},
{
selector:
"ExportAllDeclaration[source.value=/\\/events\\/handlers\\//]",
message:
"Event handlers (events/handlers/*.handler.ts) must not be re-exported. Wire them only inside the consumer feature's bind-production / bind-dev-seed (Rule E1).",
},
],
},
},
];