Two CLAUDE.md conventions had no mechanical gate, so both drifted: entity models shipped without sibling tests, and feature test files imported src modules via `../` instead of the `@/` alias. - `entity-must-have-test` — every entities/models/<x>.ts needs a sibling <x>.test.ts (errors and barrels excluded). - `no-relative-parent-import-in-tests` — feature test files must import src via `@/`, not `../`. Scoped to feature packages; core packages are governed by their own generator templates. Both register at warn level, bringing the conformance rule count to 15.
196 lines
7.5 KiB
JavaScript
196 lines
7.5 KiB
JavaScript
import js from "@eslint/js";
|
|
import eslintConfigPrettier from "eslint-config-prettier";
|
|
import tseslint from "typescript-eslint";
|
|
import turboPlugin from "eslint-plugin-turbo";
|
|
import boundaries from "eslint-plugin-boundaries";
|
|
import globals from "globals";
|
|
import conformancePlugin from "./plugin.js";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
// <gen:realtime-rules-imports>
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = path.resolve(__dirname, "..", "..");
|
|
|
|
export default [
|
|
{
|
|
ignores: [
|
|
"dist/**",
|
|
"node_modules/**",
|
|
".next/**",
|
|
".turbo/**",
|
|
"storybook-static/**",
|
|
],
|
|
},
|
|
js.configs.recommended,
|
|
{
|
|
files: ["**/*.{mjs,cjs,js}", "**/*.config.{ts,tsx}"],
|
|
languageOptions: {
|
|
globals: { ...globals.node },
|
|
},
|
|
},
|
|
...tseslint.configs.recommended,
|
|
eslintConfigPrettier,
|
|
{
|
|
plugins: { turbo: turboPlugin },
|
|
rules: {
|
|
"turbo/no-undeclared-env-vars": "warn",
|
|
},
|
|
},
|
|
{
|
|
plugins: { conformance: conformancePlugin },
|
|
rules: {
|
|
// Structural conformance rules (milestone iii.a).
|
|
// All 5 features now have manifests; promoted to ERROR.
|
|
"conformance/feature-must-have-manifest": ["error", { repoRoot }],
|
|
"conformance/usecase-must-have-test-file": "error",
|
|
"conformance/required-cores-installed": ["error", { repoRoot }],
|
|
"conformance/no-undeclared-event-publish": ["warn", { repoRoot }],
|
|
"conformance/no-undeclared-audit": ["warn", { repoRoot }],
|
|
"conformance/no-undeclared-analytics-event": ["warn", { repoRoot }],
|
|
"conformance/usecase-must-be-wired": ["error", { repoRoot }],
|
|
"conformance/component-must-have-story": "warn",
|
|
"conformance/component-must-have-test": "warn",
|
|
"conformance/atomic-tier-import-direction": "warn",
|
|
"conformance/pii-declaration-must-be-complete": "warn",
|
|
"conformance/no-undeclared-consent-check": ["warn", { repoRoot }],
|
|
"conformance/no-undeclared-rate-limit": ["warn", { repoRoot }],
|
|
"conformance/entity-must-have-test": "warn",
|
|
"conformance/no-relative-parent-import-in-tests": "warn",
|
|
},
|
|
},
|
|
{
|
|
rules: {
|
|
// Honour the leading-underscore convention for intentionally-unused params/vars.
|
|
"@typescript-eslint/no-unused-vars": [
|
|
"error",
|
|
{
|
|
argsIgnorePattern: "^_",
|
|
varsIgnorePattern: "^_",
|
|
caughtErrorsIgnorePattern: "^_",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
plugins: { boundaries },
|
|
settings: {
|
|
"boundaries/elements": [
|
|
{ type: "app", pattern: "apps/*" },
|
|
{ type: "tooling", pattern: "packages/core-eslint" },
|
|
{ type: "tooling", pattern: "packages/core-typescript" },
|
|
{ type: "tooling", pattern: "packages/core-testing" },
|
|
{ type: "core-composition", pattern: "packages/core-api" },
|
|
{ type: "core-composition", pattern: "packages/core-cms" },
|
|
{ type: "core", pattern: "packages/core-*" },
|
|
{ type: "feature", pattern: "packages/!(core-*)" },
|
|
],
|
|
},
|
|
rules: {
|
|
"boundaries/element-types": [
|
|
2,
|
|
{
|
|
default: "disallow",
|
|
rules: [
|
|
{
|
|
from: "app",
|
|
allow: ["app", "core", "core-composition", "feature", "tooling"],
|
|
},
|
|
{ from: "feature", allow: ["core", "tooling"] },
|
|
{ from: "core", allow: ["core", "tooling"] },
|
|
{ from: "core-composition", allow: ["core", "feature", "tooling"] },
|
|
{ from: "tooling", allow: ["tooling"] },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
// Block direct @sentry/* imports outside the allowlisted instrumentation paths.
|
|
{
|
|
files: ["**/*.{ts,tsx,mjs,cjs,js}"],
|
|
rules: {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
patterns: [
|
|
{
|
|
group: ["@sentry/*"],
|
|
message:
|
|
"Import from @repo/core-shared/instrumentation instead — feature packages must not depend on Sentry directly.",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
// Allowlist — the only paths permitted to import @sentry/*.
|
|
// Per ADR-017, server-side Sentry SDK usage is limited to the OTel bridge
|
|
// and browser/client init files. Patterns are double-star prefixed so they
|
|
// match whether eslint runs from the repo root or from inside a sub-package.
|
|
{
|
|
files: [
|
|
// OTel bridge — the only server-side file that may import @sentry/opentelemetry
|
|
"**/instrumentation/otel/sentry-bridge.{ts,js}",
|
|
"**/instrumentation/otel/sentry-bridge.test.{ts,js}",
|
|
// OTel DI binder — may import @sentry/opentelemetry via sentry-bridge
|
|
"**/instrumentation/di/bind-otel-instrumentation.{ts,js}",
|
|
"**/instrumentation/di/bind-otel-instrumentation.test.{ts,js}",
|
|
// Browser-side Sentry init helpers (server-only migration — browser keeps Sentry SDK directly)
|
|
"**/instrumentation/sentry/init-client.{ts,js}",
|
|
"**/instrumentation/sentry/init-client.test.{ts,js}",
|
|
"**/instrumentation/sentry/init-client-react.{ts,js}",
|
|
"**/instrumentation/sentry/init-client-react.test.{ts,js}",
|
|
// Server-side Sentry SDK init still used by apps (calls Sentry.init with DSN)
|
|
"**/instrumentation/sentry/init-server.{ts,js}",
|
|
"**/instrumentation/sentry/init-server.test.{ts,js}",
|
|
"**/instrumentation/sentry/init-server-node.{ts,js}",
|
|
"**/instrumentation/sentry/init-server-node.test.{ts,js}",
|
|
// Test guard — mocks Sentry + OTel SDKs to prevent real init in test processes
|
|
"**/setup/no-instrumentation.{ts,js}",
|
|
"**/setup/no-instrumentation.test.{ts,js}",
|
|
// Legacy alias for one release cycle
|
|
"**/setup/no-sentry.{ts,js}",
|
|
"**/setup/no-sentry.test.{ts,js}",
|
|
// App-level instrumentation entry points and build config
|
|
"**/instrumentation.{ts,js,mjs}",
|
|
"**/instrumentation-client.{ts,js,mjs}",
|
|
"**/next.config.{mjs,ts,js}",
|
|
"**/vite.config.{ts,mjs,js}",
|
|
"**/sentry.*.config.{ts,mjs,js}",
|
|
],
|
|
rules: {
|
|
"no-restricted-imports": "off",
|
|
},
|
|
},
|
|
// OTel SDK packages (@opentelemetry/sdk-*, @opentelemetry/resources,
|
|
// @opentelemetry/semantic-conventions, @opentelemetry/instrumentation-*,
|
|
// @sentry/opentelemetry) are restricted to core-shared/instrumentation/otel/
|
|
// and app-level init paths.
|
|
// The vendor-neutral API packages (@opentelemetry/api, @opentelemetry/api-logs)
|
|
// are unrestricted within core-shared/instrumentation/ — features use them for
|
|
// advanced tracing without coupling to the SDK.
|
|
{
|
|
files: [
|
|
"**/instrumentation/otel/**/*.{ts,tsx,mjs,cjs,js}",
|
|
// App-level init and build config also allowed to import OTel SDK packages
|
|
"**/instrumentation.{ts,js,mjs}",
|
|
"**/next.config.{mjs,ts,js}",
|
|
"**/vite.config.{ts,mjs,js}",
|
|
],
|
|
rules: {
|
|
"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.
|
|
// Events + jobs rules are added here when @repo/core-events is scaffolded
|
|
// via `pnpm turbo gen core-package events`.
|
|
// <gen:events-rules>
|
|
// R2 / R1 (ADR-016) — realtime-specific ESLint rules (no-direct-socket-io,
|
|
// no-realtime-handler-reexport) are added here when @repo/core-realtime is
|
|
// scaffolded via `pnpm turbo gen core-package realtime`.
|
|
// <gen:realtime-rules>
|
|
];
|