8459e1661b
Fix eslint.config.js: add import/resolver settings so boundaries plugin can resolve .ts/.tsx imports, and merge no-restricted-imports blocks to prevent ESLint 9 flat config from overriding earlier rule definitions.
131 lines
4.0 KiB
TypeScript
131 lines
4.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { execSync } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import crypto from "node:crypto";
|
|
|
|
const ROOT = path.resolve(import.meta.dirname, "../..");
|
|
|
|
/** Short random suffix to avoid collisions when tests run in parallel. */
|
|
function uid(): string {
|
|
return crypto.randomBytes(4).toString("hex");
|
|
}
|
|
|
|
/**
|
|
* Removes a file and any empty parent directories up to (but not including) the src/ tree root.
|
|
*/
|
|
function cleanupProbe(absPath: string): void {
|
|
try {
|
|
fs.unlinkSync(absPath);
|
|
} catch {
|
|
/* file may not exist */
|
|
}
|
|
let dir = path.dirname(absPath);
|
|
const srcDir = path.join(ROOT, "src");
|
|
while (dir.length > srcDir.length) {
|
|
try {
|
|
fs.rmdirSync(dir);
|
|
} catch {
|
|
break;
|
|
}
|
|
dir = path.dirname(dir);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a probe source file, optionally a target file, runs ESLint, and cleans up.
|
|
*/
|
|
function lintProbe(
|
|
sourcePath: string,
|
|
sourceContent: string,
|
|
targetPath?: string,
|
|
targetContent?: string,
|
|
): string {
|
|
const absSrc = path.join(ROOT, sourcePath);
|
|
const absTarget = targetPath ? path.join(ROOT, targetPath) : null;
|
|
fs.mkdirSync(path.dirname(absSrc), { recursive: true });
|
|
fs.writeFileSync(absSrc, sourceContent, "utf8");
|
|
if (absTarget && targetContent) {
|
|
fs.mkdirSync(path.dirname(absTarget), { recursive: true });
|
|
fs.writeFileSync(absTarget, targetContent, "utf8");
|
|
}
|
|
try {
|
|
execSync(`pnpm exec eslint "${absSrc}" --format json`, {
|
|
cwd: ROOT,
|
|
encoding: "utf8",
|
|
stdio: ["pipe", "pipe", "pipe"],
|
|
});
|
|
return "PASS";
|
|
} catch (err: unknown) {
|
|
const error = err as { stdout?: string };
|
|
return error.stdout ?? "UNKNOWN_ERROR";
|
|
} finally {
|
|
cleanupProbe(absSrc);
|
|
if (absTarget) {
|
|
cleanupProbe(absTarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
const TARGET_CONTENT = "export const something = 1;\n";
|
|
|
|
describe("boundaries rules", () => {
|
|
it("features/ cannot import from routes/", () => {
|
|
const id = uid();
|
|
const result = lintProbe(
|
|
`src/features/online-board/__bnd_probe_${id}__.ts`,
|
|
`import { something } from "../../routes/__probe_${id}__/__target_probe__";\nexport const x = something;\n`,
|
|
`src/routes/__probe_${id}__/__target_probe__.ts`,
|
|
TARGET_CONTENT,
|
|
);
|
|
expect(result).toContain("boundaries/element-types");
|
|
});
|
|
|
|
it("features/ cannot import from mf/", () => {
|
|
const id = uid();
|
|
const result = lintProbe(
|
|
`src/features/online-board/__bnd_probe_${id}__.ts`,
|
|
`import { something } from "../../mf/expose/__target_probe_${id}__";\nexport const x = something;\n`,
|
|
`src/mf/expose/__target_probe_${id}__.ts`,
|
|
TARGET_CONTENT,
|
|
);
|
|
expect(result).toContain("boundaries/element-types");
|
|
});
|
|
|
|
it("ui/ cannot import from features/", () => {
|
|
const id = uid();
|
|
const result = lintProbe(
|
|
`src/ui/__probe_${id}__/__bnd_probe__.ts`,
|
|
'import { something } from "../../features/online-board/index";\nexport const x = something;\n',
|
|
);
|
|
expect(result).toContain("boundaries/element-types");
|
|
});
|
|
|
|
it("shared/ cannot import from features/", () => {
|
|
const id = uid();
|
|
const result = lintProbe(
|
|
`src/shared/__probe_${id}__/__bnd_probe__.ts`,
|
|
'import { something } from "../../features/online-board/index";\nexport const x = something;\n',
|
|
);
|
|
expect(result).toContain("boundaries/element-types");
|
|
});
|
|
|
|
it("observability/ cannot import from features/", () => {
|
|
const id = uid();
|
|
const result = lintProbe(
|
|
`src/observability/logger/__bnd_probe_${id}__.ts`,
|
|
'import { something } from "../../features/online-board/index";\nexport const x = something;\n',
|
|
);
|
|
expect(result).toContain("boundaries/element-types");
|
|
});
|
|
|
|
it("features/ CAN import from env/ (allowed direction)", () => {
|
|
const id = uid();
|
|
const result = lintProbe(
|
|
`src/features/online-board/__bnd_probe_${id}__.ts`,
|
|
'import { getEnv } from "../../env/index";\nexport const x = getEnv;\n',
|
|
);
|
|
expect(result).not.toContain("boundaries/element-types");
|
|
});
|
|
});
|