Skip to content

Commit 968ee7b

Browse files
committed
fix: Cover edge case issues with case-insensitive filesystems
1 parent bf61058 commit 968ee7b

File tree

3 files changed

+90
-19
lines changed

3 files changed

+90
-19
lines changed

src/utils/general-utils.ts

-15
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,15 @@
11
import url from "url";
22
import path from "path";
3-
import { realpathSync } from "fs";
43

54
/* ****************************************************************************************************************** *
65
* General Utilities & Helpers
76
* ****************************************************************************************************************** */
87

98
export const isURL = (s: string): boolean => !!s && (!!url.parse(s).host || !!url.parse(s).hostname);
109

11-
export const cast = <T>(v: any): T => v;
12-
1310
export const isBaseDir = (baseDir: string, testDir: string): boolean => {
1411
const relative = path.relative(baseDir, testDir);
1512
return relative ? !relative.startsWith("..") && !path.isAbsolute(relative) : true;
1613
};
1714

1815
export const maybeAddRelativeLocalPrefix = (p: string) => (p[0] === "." ? p : `./${p}`);
19-
20-
export function tryRealpathNative(value: string) {
21-
try {
22-
return realpathSync.native(value);
23-
} catch {
24-
return value;
25-
}
26-
}
27-
28-
export function nativeRelativePath(from: string, to: string) {
29-
return path.relative(tryRealpathNative(from), tryRealpathNative(to));
30-
}

src/utils/get-relative-path.ts

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import fs from "fs";
2+
import * as os from "os";
3+
import path from "path";
4+
5+
/* ****************************************************************************************************************** */
6+
// region: Locals
7+
/* ****************************************************************************************************************** */
8+
9+
let isCaseSensitiveFilesystem: boolean | undefined;
10+
11+
// endregion
12+
13+
/* ****************************************************************************************************************** */
14+
// region: Helpers
15+
/* ****************************************************************************************************************** */
16+
17+
function tryRmFile(fileName: string) {
18+
try {
19+
if (fs.existsSync(fileName)) fs.rmSync(fileName, { force: true });
20+
} catch {}
21+
}
22+
23+
function getIsFsCaseSensitive() {
24+
if (isCaseSensitiveFilesystem != null) return isCaseSensitiveFilesystem;
25+
26+
for (let i = 0; i < 1000; i++) {
27+
const tmpFileName = path.join(os.tmpdir(), `tstp~${i}.tmp`);
28+
29+
tryRmFile(tmpFileName);
30+
31+
try {
32+
fs.writeFileSync(tmpFileName, "");
33+
isCaseSensitiveFilesystem = !fs.existsSync(tmpFileName.replace("tstp", "TSTP"));
34+
return isCaseSensitiveFilesystem;
35+
} catch {
36+
} finally {
37+
tryRmFile(tmpFileName);
38+
}
39+
}
40+
41+
console.warn(
42+
`Could not determine filesystem's case sensitivity. Please file a bug report with your system's details`
43+
);
44+
isCaseSensitiveFilesystem = false;
45+
46+
return isCaseSensitiveFilesystem;
47+
}
48+
49+
function getMatchPortion(from: string, to: string) {
50+
const lowerFrom = from.toLocaleLowerCase();
51+
const lowerTo = to.toLocaleLowerCase();
52+
53+
const maxLen = Math.max(lowerFrom.length, lowerTo.length);
54+
55+
let i = 0;
56+
while (i < maxLen) {
57+
if (lowerFrom[i] !== lowerTo[i]) break;
58+
i++;
59+
}
60+
61+
return from.slice(0, i);
62+
}
63+
64+
// endregion
65+
66+
/* ****************************************************************************************************************** */
67+
// region: Utils
68+
/* ****************************************************************************************************************** */
69+
70+
export function getRelativePath(from: string, to: string) {
71+
try {
72+
from = fs.realpathSync.native(from);
73+
to = fs.realpathSync.native(to);
74+
} catch {
75+
if (!getIsFsCaseSensitive()) {
76+
const matchPortion = getMatchPortion(from, to);
77+
from = matchPortion + from.slice(matchPortion.length);
78+
to = matchPortion + to.slice(matchPortion.length);
79+
}
80+
}
81+
82+
return path.relative(from, to);
83+
}
84+
85+
// endregion

src/utils/resolve-module-name.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { VisitorContext } from "../types";
2-
import { isBaseDir, isURL, maybeAddRelativeLocalPrefix, nativeRelativePath } from "./general-utils";
2+
import { isBaseDir, isURL, maybeAddRelativeLocalPrefix } from "./general-utils";
33
import * as path from "path";
44
import { removeFileExtension, removeSuffix, ResolvedModuleFull, SourceFile } from "typescript";
55
import { getOutputDirForSourceFile } from "./ts-helpers";
6+
import { getRelativePath } from "./get-relative-path";
67

78
/* ****************************************************************************************************************** */
89
// region: Types
@@ -167,12 +168,12 @@ export function resolveModuleName(context: VisitorContext, moduleName: string):
167168

168169
/* Remove base dirs to make relative to root */
169170
if (fileRootDir && moduleRootDir) {
170-
srcFileOutputDir = nativeRelativePath(fileRootDir, srcFileOutputDir);
171-
moduleFileOutputDir = nativeRelativePath(moduleRootDir, moduleFileOutputDir);
171+
srcFileOutputDir = getRelativePath(fileRootDir, srcFileOutputDir);
172+
moduleFileOutputDir = getRelativePath(moduleRootDir, moduleFileOutputDir);
172173
}
173174
}
174175

175-
const outputDir = nativeRelativePath(srcFileOutputDir, moduleFileOutputDir);
176+
const outputDir = getRelativePath(srcFileOutputDir, moduleFileOutputDir);
176177

177178
/* Compose final output path */
178179
const outputPath = maybeAddRelativeLocalPrefix(tsInstance.normalizePath(path.join(outputDir, outputBaseName)));

0 commit comments

Comments
 (0)