From 5b766242bebfd128cce35abf15ed412cfbcb109a Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Fri, 8 May 2026 12:18:14 +0200 Subject: [PATCH] feat(core-eslint): rules E1 (no handler re-exports) and J (no direct payload.jobs) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- packages/core-eslint/base.js | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/packages/core-eslint/base.js b/packages/core-eslint/base.js index e1607f2..351361e 100644 --- a/packages/core-eslint/base.js +++ b/packages/core-eslint/base.js @@ -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).", + }, + ], + }, + }, ];