-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Expand file tree
/
Copy pathpath.ts
More file actions
137 lines (123 loc) · 5 KB
/
Copy pathpath.ts
File metadata and controls
137 lines (123 loc) · 5 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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { NEXT_JS_FILE_EXTENSIONS } from '@onlook/constants';
import { RouterType } from '@onlook/models';
import isSubdir from 'is-subdir';
import path from 'path';
// Utility to normalize paths for comparison (handles Windows and POSIX)
function normalize(p: string): string {
if (typeof p !== 'string' || !p) return '';
let np = p.replace(/\\/g, '/');
// Lowercase drive letter for Windows
if (typeof np === 'string' && np.length > 0 && /^[A-Za-z]:\//.test(np)) {
np = np[0]?.toLowerCase() + np.slice(1);
}
return np;
}
// See: https://www.npmjs.com/package/is-subdir
// isSubdir(parentDir, subdir) returns true if subdir is the same as or inside parentDir
export function isSubdirectory(filePath: string, directories: string[]): boolean {
const absFilePath = path.resolve(filePath);
const normFilePath = normalize(absFilePath);
for (const directory of directories) {
const absDirectory = path.resolve(directory);
const normDirectory = normalize(absDirectory);
// Standard is-subdir check
if (isSubdir(normDirectory, normFilePath)) {
return true;
}
// If directory is a simple name (like 'foo' or '.git'), check if filePath contains it as a segment
if (
!directory.includes(path.sep) &&
!directory.includes('/') &&
!directory.includes('\\')
) {
const segments = normFilePath.split('/');
if (segments.includes(directory)) {
return true;
}
}
// Enhanced: handle mixed absolute/relative by checking if directory segments appear in file path
const dirSegments = normalize(directory).split('/').filter(Boolean);
const fileSegments = normFilePath.split('/').filter(Boolean);
if (dirSegments.length > 0 && fileSegments.length >= dirSegments.length) {
for (let i = 0; i <= fileSegments.length - dirSegments.length; i++) {
let match = true;
for (let j = 0; j < dirSegments.length; j++) {
if (fileSegments[i + j] !== dirSegments[j]) {
match = false;
break;
}
}
if (match) {
return true;
}
}
}
}
return false;
}
// Utility to check if a file matches the conditions
export const isTargetFile = (
targetFile: string,
conditions: { fileName: string; targetExtensions: string[]; potentialPaths: string[] },
): boolean => {
const { fileName, targetExtensions, potentialPaths } = conditions;
const fileExtWithDot = path.extname(targetFile);
if (!fileExtWithDot) {
return false;
}
const hasValidExtension = targetExtensions.some((ext) =>
ext.startsWith('.') ? ext === fileExtWithDot : ext === fileExtWithDot.slice(1),
);
if (!hasValidExtension) {
return false;
}
const baseName = path.basename(targetFile, fileExtWithDot);
if (baseName !== fileName) {
return false;
}
const dirName = normalize(path.dirname(targetFile));
return potentialPaths.some((p) => {
const normalizedP = normalize(p);
if (normalizedP === dirName) return true;
// Support Next.js route groups: match files inside (group) subdirectories
// e.g., app/(marketing)/layout.tsx should match potentialPath 'app'
const parentDir = normalize(path.dirname(dirName));
const groupDir = path.basename(dirName);
return parentDir === normalizedP && /^\([^)]+\)$/.test(groupDir);
});
};
export const isRootLayoutFile = (
filePath: string,
routerType: RouterType = RouterType.APP,
): boolean => {
const potentialPaths =
routerType === RouterType.APP ? ['app', 'src/app'] : ['pages', 'src/pages'];
return isTargetFile(filePath, {
fileName: 'layout',
targetExtensions: NEXT_JS_FILE_EXTENSIONS,
potentialPaths,
});
};
/**
* Compare two file paths for equality, handling different formats robustly
* Normalizes both paths before comparison to handle leading slashes, double slashes, etc.
*
* Examples:
* - pathsEqual('/src/app/page.tsx', 'src/app/page.tsx') => true
* - pathsEqual('src//app/page.tsx', 'src/app/page.tsx') => true
* - pathsEqual('./src/app/page.tsx', 'src/app/page.tsx') => true
*/
export function pathsEqual(path1: string | undefined | null, path2: string | undefined | null): boolean {
if (!path1 || !path2) return false;
const normalizeForComparison = (p: string) => {
const clean = p.startsWith('/') ? p.substring(1) : p;
return path.posix.normalize(clean);
};
return normalizeForComparison(path1) === normalizeForComparison(path2);
}
export function pathMatchesAny(targetPath: string, paths: string[]): boolean {
return paths.some(p => pathsEqual(targetPath, p));
}
export function findMatchingPath(targetPath: string, paths: string[]): string | undefined {
return paths.find(p => pathsEqual(targetPath, p));
}