typescript ~6.0.3 (not 7.x): typescript-eslint 8.70 peers typescript <6.1.0, so 7.x would break lint. eslint + typescript-eslint: strict type-checked lint; @eslint/js: base rules. vitest: tests next to code; vite + @preact/preset-vite: frontend bundle; preact: UI. fastify + @fastify/static: HTTP API/SSE and serving the built UI; zod: validation at boundaries. @types/node: Node 24 typings. SQLite uses the built-in node:sqlite, so no native dependency. Layer rule domain -> app -> infra -> http/web lives in tools/layer-rules.ts and is tested. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
49 lines
1.9 KiB
TypeScript
49 lines
1.9 KiB
TypeScript
import { ESLint } from 'eslint';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { layerRules } from './layer-rules.ts';
|
|
|
|
const eslint = new ESLint({ overrideConfigFile: true, overrideConfig: layerRules });
|
|
|
|
async function violations(filePath: string, importPath: string): Promise<string[]> {
|
|
const [result] = await eslint.lintText(`import x from '${importPath}';\nexport { x };\n`, { filePath });
|
|
return (result?.messages ?? []).map((message) => message.message);
|
|
}
|
|
|
|
describe('layer rule (domain -> app -> infra -> http/web)', () => {
|
|
it.each([
|
|
['src/domain/a.ts', '../app/driver.ts'],
|
|
['src/domain/a.ts', '../infra/sqlite/open.ts'],
|
|
['src/domain/a.ts', '../http/server.ts'],
|
|
['src/domain/a.ts', '../web/main.tsx'],
|
|
['src/domain/a.ts', 'node:fs'],
|
|
['src/app/a.ts', '../infra/sqlite/open.ts'],
|
|
['src/app/a.ts', '../http/server.ts'],
|
|
['src/app/a.ts', '../web/main.tsx'],
|
|
['src/infra/a.ts', '../http/server.ts'],
|
|
['src/infra/a.ts', '../web/main.tsx'],
|
|
['src/http/a.ts', '../web/main.tsx'],
|
|
['src/web/a.tsx', '../app/driver.ts'],
|
|
['src/web/a.tsx', '../infra/sqlite/open.ts'],
|
|
['src/web/a.tsx', '../http/server.ts'],
|
|
['src/web/a.tsx', 'node:fs'],
|
|
])('%s must not import %s', async (file, target) => {
|
|
expect(await violations(file, target)).toHaveLength(1);
|
|
});
|
|
|
|
it.each([
|
|
['src/app/a.ts', '../domain/steps.ts'],
|
|
['src/infra/a.ts', '../domain/steps.ts'],
|
|
['src/infra/a.ts', '../app/driver.ts'],
|
|
['src/infra/a.ts', 'node:sqlite'],
|
|
['src/http/a.ts', '../app/driver.ts'],
|
|
['src/http/a.ts', '../infra/sqlite/open.ts'],
|
|
['src/web/a.tsx', '../domain/steps.ts'],
|
|
['src/web/a.tsx', '../http/contract.ts'],
|
|
['src/main/a.ts', '../infra/sqlite/open.ts'],
|
|
['src/main/a.ts', '../http/server.ts'],
|
|
['src/app/a.test.ts', '../infra/sqlite/open.ts'],
|
|
])('%s may import %s', async (file, target) => {
|
|
expect(await violations(file, target)).toEqual([]);
|
|
});
|
|
});
|