feat(generators): capture realtime ESLint rules as template files
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
// packages/core-eslint/rules/no-direct-socket-io.js
|
||||||
|
const ALLOWED = [
|
||||||
|
/\/packages\/core-realtime\/src\//,
|
||||||
|
/\/apps\/[^/]+\/server\.ts$/,
|
||||||
|
/\/apps\/[^/]+\/src\/.*\.test\.ts$/,
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
meta: {
|
||||||
|
type: "problem",
|
||||||
|
docs: { description: "Block direct socket.io imports outside core-realtime + app servers" },
|
||||||
|
messages: {
|
||||||
|
noDirectSocketIO: 'Import from "@repo/core-realtime" instead of "socket.io". Direct imports allowed only in packages/core-realtime/src/ and apps/*/server.ts.',
|
||||||
|
noDirectSocketIOClient: 'Use the realtime helpers from "@repo/core-realtime" / "@repo/core-testing/instrumentation" instead of "socket.io-client".',
|
||||||
|
},
|
||||||
|
schema: [],
|
||||||
|
},
|
||||||
|
create(context) {
|
||||||
|
const filename = context.filename ?? context.getFilename();
|
||||||
|
const allowed = ALLOWED.some((re) => re.test(filename));
|
||||||
|
if (allowed) return {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
ImportDeclaration(node) {
|
||||||
|
const source = node.source.value;
|
||||||
|
if (source === "socket.io") {
|
||||||
|
context.report({ node, messageId: "noDirectSocketIO" });
|
||||||
|
} else if (source === "socket.io-client") {
|
||||||
|
context.report({ node, messageId: "noDirectSocketIOClient" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// packages/core-eslint/rules/no-direct-socket-io.test.js
|
||||||
|
import { RuleTester } from "eslint";
|
||||||
|
import rule from "./no-direct-socket-io.js";
|
||||||
|
|
||||||
|
const tester = new RuleTester({
|
||||||
|
languageOptions: { ecmaVersion: 2022, sourceType: "module" },
|
||||||
|
});
|
||||||
|
|
||||||
|
tester.run("no-direct-socket-io", rule, {
|
||||||
|
valid: [
|
||||||
|
// Allowed inside core-realtime
|
||||||
|
{ code: 'import { Server } from "socket.io";', filename: "/repo/packages/core-realtime/src/socket-io-realtime-server.ts" },
|
||||||
|
// Allowed in app servers
|
||||||
|
{ code: 'import { Server } from "socket.io";', filename: "/repo/apps/web-next/server.ts" },
|
||||||
|
// Allowed in app integration tests (e.g. realtime-ping e2e)
|
||||||
|
{ code: 'import { Server } from "socket.io";', filename: "/repo/apps/web-next/src/__tests__/realtime-ping.test.ts" },
|
||||||
|
{ code: 'import { io } from "socket.io-client";', filename: "/repo/apps/web-next/src/__tests__/realtime-ping.test.ts" },
|
||||||
|
// Allowed elsewhere when not importing socket.io
|
||||||
|
{ code: 'import { foo } from "bar";', filename: "/repo/packages/blog/src/foo.ts" },
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
code: 'import { Server } from "socket.io";',
|
||||||
|
filename: "/repo/packages/blog/src/foo.ts",
|
||||||
|
errors: [{ messageId: "noDirectSocketIO" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'import { io } from "socket.io-client";',
|
||||||
|
filename: "/repo/packages/blog/src/ui/Component.tsx",
|
||||||
|
errors: [{ messageId: "noDirectSocketIOClient" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// packages/core-eslint/rules/no-realtime-handler-reexport.js
|
||||||
|
// R1 — Realtime handlers are private. A feature's realtime/handlers/*.handler.ts
|
||||||
|
// must only be wired in the feature's own bind-production / bind-dev-seed files.
|
||||||
|
// They must never be re-exported from barrel files or other public surfaces.
|
||||||
|
|
||||||
|
const BIND_FILE = /\bdi\/bind-(?:production|dev-seed)\b/;
|
||||||
|
const REALTIME_HANDLERS_IN_SOURCE = /\/realtime\/handlers\//;
|
||||||
|
const HANDLERS_IN_SOURCE = /\/handlers\//;
|
||||||
|
const REALTIME_IN_FILENAME = /\/realtime\//;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
meta: {
|
||||||
|
type: "problem",
|
||||||
|
docs: {
|
||||||
|
description:
|
||||||
|
"Block re-exports of realtime/handlers/** outside feature bind-* files (ADR-016 R1)",
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
noRealtimeHandlerReexport:
|
||||||
|
"Realtime handlers (realtime/handlers/*.handler.ts) must not be re-exported (ADR-016 R1). " +
|
||||||
|
"Wire them only inside the feature's own bind-production / bind-dev-seed files.",
|
||||||
|
},
|
||||||
|
schema: [],
|
||||||
|
},
|
||||||
|
create(context) {
|
||||||
|
const filename = context.filename ?? context.getFilename();
|
||||||
|
|
||||||
|
// Bind-* files are the only allowed place for these exports/imports
|
||||||
|
if (BIND_FILE.test(filename)) return {};
|
||||||
|
|
||||||
|
function checkExportSource(node) {
|
||||||
|
if (!node.source) return;
|
||||||
|
const source = node.source.value;
|
||||||
|
const isRealtimeHandler =
|
||||||
|
REALTIME_HANDLERS_IN_SOURCE.test(source) ||
|
||||||
|
(HANDLERS_IN_SOURCE.test(source) && REALTIME_IN_FILENAME.test(filename));
|
||||||
|
if (isRealtimeHandler) {
|
||||||
|
context.report({ node, messageId: "noRealtimeHandlerReexport" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ExportNamedDeclaration: checkExportSource,
|
||||||
|
ExportAllDeclaration: checkExportSource,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// packages/core-eslint/rules/no-realtime-handler-reexport.test.js
|
||||||
|
import { RuleTester } from "eslint";
|
||||||
|
import rule from "./no-realtime-handler-reexport.js";
|
||||||
|
|
||||||
|
const tester = new RuleTester({
|
||||||
|
languageOptions: { ecmaVersion: 2022, sourceType: "module" },
|
||||||
|
});
|
||||||
|
|
||||||
|
tester.run("no-realtime-handler-reexport", rule, {
|
||||||
|
valid: [
|
||||||
|
// Importing a handler from inside a feature's bind-* file is allowed.
|
||||||
|
{
|
||||||
|
code: 'import { onPingHandler } from "../realtime/handlers/on-ping.handler";',
|
||||||
|
filename: "/repo/packages/blog/src/di/bind-production.ts",
|
||||||
|
},
|
||||||
|
// Re-exporting a channel descriptor is allowed.
|
||||||
|
{
|
||||||
|
code: 'export { presenceChannel } from "./realtime/presence.channel";',
|
||||||
|
filename: "/repo/packages/blog/src/index.ts",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
// Re-exporting a handler from any non-bind file is forbidden.
|
||||||
|
{
|
||||||
|
code: 'export { onPingHandler } from "./realtime/handlers/on-ping.handler";',
|
||||||
|
filename: "/repo/packages/blog/src/index.ts",
|
||||||
|
errors: [{ messageId: "noRealtimeHandlerReexport" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'export * from "./handlers/on-ping.handler";',
|
||||||
|
filename: "/repo/packages/blog/src/realtime/index.ts",
|
||||||
|
errors: [{ messageId: "noRealtimeHandlerReexport" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user