forked from rescript-lang/rescript-vscode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfind-runtime.ts
More file actions
167 lines (148 loc) · 4.89 KB
/
find-runtime.ts
File metadata and controls
167 lines (148 loc) · 4.89 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
import { readdir, stat as statAsync, readFile } from "fs/promises";
import { join, resolve } from "path";
import { compilerInfoPartialPath } from "./constants";
import { NormalizedPath } from "./utils";
// Efficient parallel folder traversal to find node_modules directories
async function findNodeModulesDirs(
rootPath: string,
maxDepth = 12,
): Promise<string[]> {
const nodeModulesDirs: string[] = [];
const stack: Array<{ dir: string; depth: number }> = [
{ dir: rootPath, depth: 0 },
];
const visited = new Set<string>();
while (stack.length) {
const { dir, depth } = stack.pop()!;
if (depth > maxDepth || visited.has(dir)) continue;
visited.add(dir);
let entries: string[];
try {
entries = await readdir(dir);
} catch {
continue;
}
if (entries.includes("node_modules")) {
const nm = join(dir, "node_modules");
try {
const st = await statAsync(nm);
if (st.isDirectory()) {
nodeModulesDirs.push(nm);
// Do NOT push deeper here to keep same behavior (stop at first node_modules in this branch)
continue;
}
} catch {}
}
for (const entry of entries) {
if (entry === "node_modules" || entry.startsWith(".")) continue;
const full = join(dir, entry);
try {
const st = await statAsync(full);
if (st.isDirectory()) {
stack.push({ dir: full, depth: depth + 1 });
}
} catch {}
}
}
return nodeModulesDirs;
}
// Custom function to find Deno or pnpm vendorized @rescript/runtime directories
async function findRescriptRuntimeInAlternativeLayout(
subfolder: ".deno" | ".pnpm",
nodeModulesPath: string,
) {
// We only care about the Deno vendorized layout:
// <nodeModulesPath>/.deno/@rescript+runtime@<version>/node_modules/@rescript/runtime
const alternativeRoot = join(nodeModulesPath, subfolder);
let entries: string[];
try {
entries = await readdir(alternativeRoot);
} catch {
return [];
}
// Collect all @rescript+runtime@<version> vendor dirs
const vendorDirs = entries.filter((e) => e.startsWith("@rescript+runtime@"));
if (vendorDirs.length === 0) return [];
// Optionally pick “latest” by version; for now we return all valid matches.
const results: string[] = [];
for (const dir of vendorDirs) {
const runtimePath = join(
alternativeRoot,
dir,
"node_modules",
"@rescript",
"runtime",
);
try {
const st = await statAsync(runtimePath);
if (st.isDirectory()) results.push(runtimePath);
} catch {
// Ignore inaccessible / missing path
}
}
return results;
}
async function findRuntimePath(
project: NormalizedPath,
): Promise<NormalizedPath[]> {
// Try a compiler-info.json file first
const compilerInfo = resolve(project, compilerInfoPartialPath);
try {
const contents = await readFile(compilerInfo, "utf8");
const compileInfo: { runtime_path?: string } = JSON.parse(contents);
if (compileInfo && compileInfo.runtime_path) {
return [compileInfo.runtime_path as NormalizedPath];
}
} catch {
// Ignore errors, fallback to node_modules search
}
// Find all node_modules directories using efficient traversal
const node_modules = await findNodeModulesDirs(project);
const rescriptRuntimeDirs = await Promise.all(
node_modules.map(async (nm) => {
const results = [];
// Check for standard layout: @rescript/runtime
const standardPath = join(nm, "@rescript", "runtime");
try {
const stat = await statAsync(standardPath);
if (stat.isDirectory()) {
results.push(standardPath);
// If we found standard layout, no need to search for pnpm or Deno layouts
return results;
}
} catch (e) {
// Directory doesn't exist, continue
}
// Only check for pnpm vendorized layouts if standard layout wasn't found
const pnpmResults = await findRescriptRuntimeInAlternativeLayout(
".pnpm",
nm,
);
results.push(...pnpmResults);
if (results.length > 0) {
return results;
}
// Only check for Deno vendorized layouts if standard layout wasn't found
const denoResults = await findRescriptRuntimeInAlternativeLayout(
".deno",
nm,
);
results.push(...denoResults);
return results;
}),
).then((results) => results.flatMap((x) => x));
return rescriptRuntimeDirs.map(
(runtime) => resolve(runtime) as NormalizedPath,
);
}
/**
* Find all installed @rescript/runtime directories in the given project path.
* In a perfect world, there should be exactly one.
* Note: This function is not cached here. Caching is handled by the caller
* (see getRuntimePathFromWorkspaceRoot in utils.ts).
*/
export async function findRescriptRuntimesInProject(
project: NormalizedPath,
): Promise<NormalizedPath[]> {
return await findRuntimePath(project);
}