-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvite-plugins.ts
More file actions
88 lines (78 loc) · 2.98 KB
/
Copy pathvite-plugins.ts
File metadata and controls
88 lines (78 loc) · 2.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import path from "node:path";
import type { Plugin } from "vite";
interface PreventImportsOptions {
/** The directory path to block imports from (can be relative or absolute) */
folder: string;
/** The directory path from which imports should be blocked (if not specified, blocks from anywhere) */
fromFolder?: string;
/** List of file paths or glob patterns to ignore from blocking */
ignores?: string[];
}
/**
* Enforces architectural boundaries by blocking imports from a directory.
*
* Leverages Vite's resolver to catch all import variations, including
* normalized paths like `../web/../server`.
*
* @example
* preventImports({ folder: './server' })
*
* @example
* preventImports({ folder: './server', ignores: ['./server/shared.ts'] })
*
* @example
* // Only block imports from web/ to server/
* preventImports({ folder: './server', fromFolder: './web' })
*/
export const preventImports = ({
folder,
fromFolder,
ignores,
}: PreventImportsOptions): Plugin => {
// Normalize the blocked path to an absolute path for consistent comparison
const blockedPath = path.resolve(folder);
// Normalize the fromFolder path if provided
const fromPath = fromFolder ? path.resolve(fromFolder) : null;
// Normalize ignore paths to absolute paths
const ignoredPaths = (ignores ?? []).map((p) => path.resolve(p));
return {
name: "vite-plugin-prevent-imports",
// ensure this runs before other resolution plugins
enforce: "pre" as const,
async resolveId(source, importer, options) {
// If there is no importer, it's an entry point (allow it)
if (!importer) return null;
// If fromFolder is specified, only block imports from files within that folder
if (fromPath && !importer.startsWith(fromPath)) {
return null;
}
// Resolve the import using Vite's internal resolver 'skipSelf' prevents
// this plugin from entering an infinite loop
const resolution = await this.resolve(source, importer, {
skipSelf: true,
...options,
});
// If resolution fails or is external, let Vite handle it
if (!resolution || !resolution.id) return null;
// Check if the resolved absolute path starts with the blocked directory
// Using .startsWith is safer than .includes to prevent false positives
if (resolution.id.startsWith(blockedPath)) {
// Check if the resolved path is in the ignore list
const isIgnored = ignoredPaths.some(
(ignoredPath) =>
resolution.id === ignoredPath ||
resolution.id.startsWith(ignoredPath + path.sep),
);
if (!isIgnored) {
throw new Error(
`\n🚨 SECURITY ERROR: Blocked import detected.\n` +
` File: ${importer}\n` +
` Attempted to import: ${source}\n` +
` Resolved to blocked path: ${resolution.id}\n`,
);
}
}
return null; // Return null to allow other plugins/Vite to handle valid imports
},
};
};