-
Notifications
You must be signed in to change notification settings - Fork 63
Expand file tree
/
Copy pathutils.ts
More file actions
177 lines (158 loc) · 5.22 KB
/
utils.ts
File metadata and controls
177 lines (158 loc) · 5.22 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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import * as path from "path";
import * as fs from "fs";
import * as os from "os";
import { DocumentUri } from "vscode-languageclient";
import * as semver from "semver";
import { getBinaryPathLegacy } from "./utils-legacy";
/*
* Much of the code in here is duplicated from the server code.
* At some point we should move the functionality powered by this
* to the server itself.
*/
/**
* Branded type for normalized file paths.
*
* All paths should be normalized to ensure consistent lookups and prevent
* path format mismatches (e.g., trailing slashes, relative vs absolute paths).
*
* Use `normalizePath()` to convert a regular path to a `NormalizedPath`.
*/
export type NormalizedPath = string & { __brand: "NormalizedPath" };
/**
* Normalizes a file path and returns it as a `NormalizedPath`.
*
* @param filePath - The path to normalize (can be null)
* @returns The normalized path, or null if input was null
*/
export function normalizePath(filePath: string | null): NormalizedPath | null {
// `path.normalize` ensures we can assume string is now NormalizedPath
return filePath != null ? (path.normalize(filePath) as NormalizedPath) : null;
}
type binaryName = "rescript-editor-analysis.exe" | "rescript-tools.exe";
// v12+ format: with hyphen (e.g., "darwin-arm64")
const platformTarget = `${process.platform}-${process.arch}`;
// ============================================================================
// Version Detection
// ============================================================================
/**
* Finds the ReScript version from package.json in the project.
*/
export const findReScriptVersion = (
projectRootPath: NormalizedPath | null,
): string | null => {
if (projectRootPath == null) {
return null;
}
try {
const packageJsonPath = path.join(
projectRootPath,
"node_modules",
"rescript",
"package.json",
);
if (!fs.existsSync(packageJsonPath)) {
return null;
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
return packageJson.version ?? null;
} catch {
return null;
}
};
// ============================================================================
// ReScript 12+ Binary Finding (Clean, self-contained)
// ============================================================================
/**
* Finds binaries for ReScript 12+ using @rescript/${target}/bin.js structure.
* This is the single source of truth for binary locations in v12+.
* Returns null if binary not found, throws on critical errors.
*/
const getBinaryPathReScript12 = (
projectRootPath: NormalizedPath,
binaryName: binaryName,
): string | null => {
const binJsPath = path.join(
projectRootPath,
"node_modules",
"@rescript",
platformTarget,
"bin.js",
);
if (!fs.existsSync(binJsPath)) {
return null;
}
// Read bin.js and extract the binary path
// bin.js exports binPaths object with paths to binaries
const binDir = path.join(
projectRootPath,
"node_modules",
"@rescript",
platformTarget,
"bin",
);
let binaryPath: string | null = null;
if (binaryName === "rescript-tools.exe") {
binaryPath = path.join(binDir, "rescript-tools.exe");
} else if (binaryName === "rescript-editor-analysis.exe") {
binaryPath = path.join(binDir, "rescript-editor-analysis.exe");
}
if (binaryPath != null && fs.existsSync(binaryPath)) {
return binaryPath;
}
return null;
};
// ============================================================================
// Main Binary Finding Function (Routes to v12 or legacy)
// ============================================================================
/**
* Finds a ReScript binary, routing to v12+ or legacy implementation.
* Top-level if separates the two code paths completely.
*/
export const getBinaryPath = (
binaryName: "rescript-editor-analysis.exe" | "rescript-tools.exe",
projectRootPath: NormalizedPath | null = null,
): string | null => {
const rescriptVersion = findReScriptVersion(projectRootPath);
const isReScript12OrHigher =
rescriptVersion != null &&
semver.valid(rescriptVersion) &&
semver.gte(rescriptVersion, "12.0.0");
// Top-level separation: v12+ or legacy
if (isReScript12OrHigher && projectRootPath != null) {
return getBinaryPathReScript12(projectRootPath, binaryName);
} else {
return getBinaryPathLegacy(projectRootPath, binaryName);
}
};
let tempFilePrefix = "rescript_" + process.pid + "_";
let tempFileId = 0;
export const createFileInTempDir = (prefix = "", extension = "") => {
let tempFileName = prefix + "_" + tempFilePrefix + tempFileId + extension;
tempFileId = tempFileId + 1;
return path.join(os.tmpdir(), tempFileName);
};
export let findProjectRootOfFileInDir = (
source: string,
): NormalizedPath | null => {
const normalizedSource = normalizePath(source);
if (normalizedSource == null) {
return null;
}
const dir = normalizePath(path.dirname(normalizedSource));
if (dir == null) {
return null;
}
if (
fs.existsSync(path.join(dir, "rescript.json")) ||
fs.existsSync(path.join(dir, "bsconfig.json"))
) {
return dir;
} else {
if (dir === normalizedSource) {
// reached top
return null;
} else {
return findProjectRootOfFileInDir(dir);
}
}
};