@ff-labs/fff-bun 0.1.0-nightly.00750c2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +283 -0
- package/examples/grep.ts +285 -0
- package/examples/search.ts +224 -0
- package/package.json +86 -0
- package/scripts/cli.ts +114 -0
- package/scripts/postinstall.ts +51 -0
- package/src/download.ts +252 -0
- package/src/ffi.ts +450 -0
- package/src/finder.ts +413 -0
- package/src/git-lifecycle.test.ts +291 -0
- package/src/index.test.ts +357 -0
- package/src/index.ts +83 -0
- package/src/platform.ts +121 -0
- package/src/types.ts +404 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
|
|
2
|
+
import { FileFinder } from "./index";
|
|
3
|
+
import { findBinary, getDevBinaryPath } from "./download";
|
|
4
|
+
import { getTriple, getLibExtension, getLibFilename } from "./platform";
|
|
5
|
+
|
|
6
|
+
// Cross-platform path normalization helpers
|
|
7
|
+
const normalizePath = (path: string | null | undefined): string | null => {
|
|
8
|
+
if (!path) return null;
|
|
9
|
+
// Convert backslashes to forward slashes for consistent comparison
|
|
10
|
+
return path.replace(/\\/g, "/");
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const testDir = process.cwd();
|
|
14
|
+
|
|
15
|
+
describe("Platform Detection", () => {
|
|
16
|
+
test("getTriple returns valid triple", () => {
|
|
17
|
+
const triple = getTriple();
|
|
18
|
+
expect(triple).toMatch(
|
|
19
|
+
/^(x86_64|aarch64|arm)-(apple-darwin|unknown-linux-(gnu|musl)|pc-windows-msvc)$/,
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("getLibExtension returns correct extension", () => {
|
|
24
|
+
const ext = getLibExtension();
|
|
25
|
+
const platform = process.platform;
|
|
26
|
+
|
|
27
|
+
if (platform === "darwin") {
|
|
28
|
+
expect(ext).toBe("dylib");
|
|
29
|
+
} else if (platform === "win32") {
|
|
30
|
+
expect(ext).toBe("dll");
|
|
31
|
+
} else {
|
|
32
|
+
expect(ext).toBe("so");
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("getLibFilename returns correct filename", () => {
|
|
37
|
+
const filename = getLibFilename();
|
|
38
|
+
const ext = getLibExtension();
|
|
39
|
+
|
|
40
|
+
if (process.platform === "win32") {
|
|
41
|
+
expect(filename).toBe(`fff_c.${ext}`);
|
|
42
|
+
} else {
|
|
43
|
+
expect(filename).toBe(`libfff_c.${ext}`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("Binary Detection", () => {
|
|
49
|
+
test("getDevBinaryPath finds local build", () => {
|
|
50
|
+
const devPath = getDevBinaryPath();
|
|
51
|
+
expect(devPath).not.toBeNull();
|
|
52
|
+
// Normalize path for cross-platform comparison (Windows uses backslashes)
|
|
53
|
+
const normalizedPath = normalizePath(devPath);
|
|
54
|
+
expect(normalizedPath).toContain("target/release");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("findBinary returns a path", () => {
|
|
58
|
+
const path = findBinary();
|
|
59
|
+
expect(path).not.toBeNull();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("FileFinder - Health Check", () => {
|
|
64
|
+
test("healthCheckStatic works without an instance", () => {
|
|
65
|
+
const result = FileFinder.healthCheckStatic();
|
|
66
|
+
expect(result.ok).toBe(true);
|
|
67
|
+
|
|
68
|
+
if (result.ok) {
|
|
69
|
+
expect(result.value.version).toBeDefined();
|
|
70
|
+
expect(result.value.git.available).toBe(true);
|
|
71
|
+
expect(result.value.filePicker.initialized).toBe(false);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("FileFinder - Full Lifecycle", () => {
|
|
77
|
+
let finder: FileFinder;
|
|
78
|
+
|
|
79
|
+
beforeAll(() => {
|
|
80
|
+
const result = FileFinder.create({ basePath: testDir });
|
|
81
|
+
expect(result.ok).toBe(true);
|
|
82
|
+
if (result.ok) {
|
|
83
|
+
finder = result.value;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterAll(() => {
|
|
88
|
+
finder?.destroy();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("create succeeds with valid path", () => {
|
|
92
|
+
expect(finder).toBeDefined();
|
|
93
|
+
expect(finder.isDestroyed).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("isScanning returns a boolean", () => {
|
|
97
|
+
const scanning = finder.isScanning();
|
|
98
|
+
expect(typeof scanning).toBe("boolean");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("getScanProgress returns valid data", () => {
|
|
102
|
+
const result = finder.getScanProgress();
|
|
103
|
+
expect(result.ok).toBe(true);
|
|
104
|
+
|
|
105
|
+
if (result.ok) {
|
|
106
|
+
expect(typeof result.value.scannedFilesCount).toBe("number");
|
|
107
|
+
expect(typeof result.value.isScanning).toBe("boolean");
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("waitForScan completes", () => {
|
|
112
|
+
// Small timeout - scan should be fast or already done
|
|
113
|
+
const result = finder.waitForScan(500);
|
|
114
|
+
expect(result.ok).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("search with empty query returns all files", () => {
|
|
118
|
+
// First check scan progress to see if files were indexed
|
|
119
|
+
const progress = finder.getScanProgress();
|
|
120
|
+
if (progress.ok) {
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const result = finder.search("");
|
|
124
|
+
expect(result.ok).toBe(true);
|
|
125
|
+
|
|
126
|
+
if (result.ok) {
|
|
127
|
+
if (result.value.items.length > 0) {
|
|
128
|
+
// Log first few paths to see format on Windows
|
|
129
|
+
// Items are strings (file paths), not objects
|
|
130
|
+
const samplePaths = result.value.items
|
|
131
|
+
.slice(0, 3)
|
|
132
|
+
.map((item) =>
|
|
133
|
+
normalizePath(typeof item === "string" ? item : item.relativePath),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
// Empty query should return files (frecency-sorted)
|
|
137
|
+
expect(result.value.totalFiles).toBeGreaterThan(0);
|
|
138
|
+
} else {
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("search returns a valid result structure", () => {
|
|
143
|
+
const result = finder.search("Cargo.toml");
|
|
144
|
+
expect(result.ok).toBe(true);
|
|
145
|
+
|
|
146
|
+
if (result.ok) {
|
|
147
|
+
expect(typeof result.value.totalMatched).toBe("number");
|
|
148
|
+
expect(typeof result.value.totalFiles).toBe("number");
|
|
149
|
+
expect(Array.isArray(result.value.items)).toBe(true);
|
|
150
|
+
expect(Array.isArray(result.value.scores)).toBe(true);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("search returns empty for non-matching query", () => {
|
|
155
|
+
const result = finder.search("xyznonexistentfilenamexyz123456");
|
|
156
|
+
expect(result.ok).toBe(true);
|
|
157
|
+
|
|
158
|
+
if (result.ok) {
|
|
159
|
+
expect(result.value.totalMatched).toBe(0);
|
|
160
|
+
expect(result.value.items.length).toBe(0);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("search respects pageSize option", () => {
|
|
165
|
+
const result = finder.search("ts", { pageSize: 3 });
|
|
166
|
+
expect(result.ok).toBe(true);
|
|
167
|
+
|
|
168
|
+
if (result.ok) {
|
|
169
|
+
expect(result.value.items.length).toBeLessThanOrEqual(3);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("liveGrep plain text returns matching lines", () => {
|
|
174
|
+
const result = finder.liveGrep("fff-core", {
|
|
175
|
+
mode: "plain",
|
|
176
|
+
});
|
|
177
|
+
expect(result.ok).toBe(true);
|
|
178
|
+
|
|
179
|
+
if (result.ok) {
|
|
180
|
+
if (result.value.items.length > 0) {
|
|
181
|
+
// Log sample match to verify content on Windows
|
|
182
|
+
const first = result.value.items[0];
|
|
183
|
+
const normalizedPath = normalizePath(first.relativePath);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
expect(result.value.totalMatched).toBeGreaterThan(0);
|
|
187
|
+
expect(result.value.items.length).toBeGreaterThan(0);
|
|
188
|
+
|
|
189
|
+
const first = result.value.items[0];
|
|
190
|
+
expect(typeof first.relativePath).toBe("string");
|
|
191
|
+
// Normalize path for cross-platform validation
|
|
192
|
+
const normalizedFirstPath = normalizePath(first.relativePath);
|
|
193
|
+
expect(normalizedFirstPath).toBeTruthy();
|
|
194
|
+
expect(typeof first.lineNumber).toBe("number");
|
|
195
|
+
expect(first.lineNumber).toBeGreaterThan(0);
|
|
196
|
+
expect(typeof first.lineContent).toBe("string");
|
|
197
|
+
expect(first.lineContent.toLowerCase()).toContain("fff-core");
|
|
198
|
+
expect(Array.isArray(first.matchRanges)).toBe(true);
|
|
199
|
+
expect(first.matchRanges.length).toBeGreaterThan(0);
|
|
200
|
+
|
|
201
|
+
expect(typeof result.value.totalFilesSearched).toBe("number");
|
|
202
|
+
expect(typeof result.value.totalFiles).toBe("number");
|
|
203
|
+
expect(typeof result.value.filteredFileCount).toBe("number");
|
|
204
|
+
} else {
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("liveGrep fuzzy mode returns results with scores", () => {
|
|
209
|
+
// Intentional typo: "depdnency" instead of "dependency" to exercise fuzzy matching
|
|
210
|
+
const result = finder.liveGrep("depdnency", {
|
|
211
|
+
mode: "fuzzy",
|
|
212
|
+
});
|
|
213
|
+
expect(result.ok).toBe(true);
|
|
214
|
+
|
|
215
|
+
if (result.ok) {
|
|
216
|
+
expect(result.value.totalMatched).toBeGreaterThan(0);
|
|
217
|
+
expect(result.value.items.length).toBeGreaterThan(0);
|
|
218
|
+
|
|
219
|
+
const first = result.value.items[0];
|
|
220
|
+
expect(typeof first.relativePath).toBe("string");
|
|
221
|
+
// Normalize path for cross-platform validation
|
|
222
|
+
const normalizedFirstPath = normalizePath(first.relativePath);
|
|
223
|
+
expect(normalizedFirstPath).toBeTruthy();
|
|
224
|
+
expect(typeof first.lineNumber).toBe("number");
|
|
225
|
+
expect(typeof first.lineContent).toBe("string");
|
|
226
|
+
// Fuzzy mode should produce a fuzzyScore on each match
|
|
227
|
+
expect(typeof first.fuzzyScore).toBe("number");
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("healthCheck shows initialized state", () => {
|
|
232
|
+
const result = finder.healthCheck();
|
|
233
|
+
expect(result.ok).toBe(true);
|
|
234
|
+
|
|
235
|
+
if (result.ok) {
|
|
236
|
+
expect(result.value.filePicker.initialized).toBe(true);
|
|
237
|
+
expect(result.value.filePicker.basePath).toBeDefined();
|
|
238
|
+
// Normalize basePath for cross-platform comparison
|
|
239
|
+
const normalizedBasePath = normalizePath(
|
|
240
|
+
result.value.filePicker.basePath || "",
|
|
241
|
+
);
|
|
242
|
+
const normalizedTestDir = normalizePath(testDir);
|
|
243
|
+
expect(normalizedBasePath).toBe(normalizedTestDir);
|
|
244
|
+
expect(typeof result.value.filePicker.indexedFiles).toBe("number");
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("healthCheck detects git repository", () => {
|
|
249
|
+
const result = finder.healthCheck(testDir);
|
|
250
|
+
expect(result.ok).toBe(true);
|
|
251
|
+
|
|
252
|
+
if (result.ok) {
|
|
253
|
+
expect(result.value.git.available).toBe(true);
|
|
254
|
+
expect(typeof result.value.git.repositoryFound).toBe("boolean");
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("destroy and re-create works", () => {
|
|
259
|
+
finder.destroy();
|
|
260
|
+
expect(finder.isDestroyed).toBe(true);
|
|
261
|
+
|
|
262
|
+
const result = FileFinder.create({ basePath: testDir });
|
|
263
|
+
expect(result.ok).toBe(true);
|
|
264
|
+
if (result.ok) {
|
|
265
|
+
finder = result.value;
|
|
266
|
+
}
|
|
267
|
+
expect(finder.isDestroyed).toBe(false);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("multiple instances can coexist", () => {
|
|
271
|
+
const result2 = FileFinder.create({ basePath: testDir });
|
|
272
|
+
expect(result2.ok).toBe(true);
|
|
273
|
+
|
|
274
|
+
if (result2.ok) {
|
|
275
|
+
const finder2 = result2.value;
|
|
276
|
+
|
|
277
|
+
// Both should work independently
|
|
278
|
+
const search1 = finder.search("Cargo");
|
|
279
|
+
const search2 = finder2.search("Cargo");
|
|
280
|
+
|
|
281
|
+
expect(search1.ok).toBe(true);
|
|
282
|
+
expect(search2.ok).toBe(true);
|
|
283
|
+
|
|
284
|
+
// Destroying one should not affect the other
|
|
285
|
+
finder2.destroy();
|
|
286
|
+
|
|
287
|
+
const search3 = finder.search("Cargo");
|
|
288
|
+
expect(search3.ok).toBe(true);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe("FileFinder - Error Handling", () => {
|
|
294
|
+
test("search fails on destroyed instance", () => {
|
|
295
|
+
const createResult = FileFinder.create({ basePath: testDir });
|
|
296
|
+
expect(createResult.ok).toBe(true);
|
|
297
|
+
if (!createResult.ok) return;
|
|
298
|
+
|
|
299
|
+
const f = createResult.value;
|
|
300
|
+
f.destroy();
|
|
301
|
+
|
|
302
|
+
const result = f.search("test");
|
|
303
|
+
expect(result.ok).toBe(false);
|
|
304
|
+
if (!result.ok) {
|
|
305
|
+
expect(result.error).toContain("destroyed");
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("getScanProgress fails on destroyed instance", () => {
|
|
310
|
+
const createResult = FileFinder.create({ basePath: testDir });
|
|
311
|
+
expect(createResult.ok).toBe(true);
|
|
312
|
+
if (!createResult.ok) return;
|
|
313
|
+
|
|
314
|
+
const f = createResult.value;
|
|
315
|
+
f.destroy();
|
|
316
|
+
|
|
317
|
+
const result = f.getScanProgress();
|
|
318
|
+
expect(result.ok).toBe(false);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("create fails with invalid path", () => {
|
|
322
|
+
// Use a cross-platform invalid path
|
|
323
|
+
const invalidPath =
|
|
324
|
+
process.platform === "win32"
|
|
325
|
+
? "C:\\nonexistent\\path\\that\\does\\not\\exist"
|
|
326
|
+
: "/nonexistent/path/that/does/not/exist";
|
|
327
|
+
|
|
328
|
+
const result = FileFinder.create({
|
|
329
|
+
basePath: invalidPath,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
expect(result.ok).toBe(false);
|
|
333
|
+
if (!result.ok) {
|
|
334
|
+
expect(result.error).toContain("Failed");
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe("Result Type Helpers", () => {
|
|
340
|
+
test("ok helper creates success result", async () => {
|
|
341
|
+
const { ok } = await import("./types");
|
|
342
|
+
const result = ok(42);
|
|
343
|
+
expect(result.ok).toBe(true);
|
|
344
|
+
if (result.ok) {
|
|
345
|
+
expect(result.value).toBe(42);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("err helper creates error result", async () => {
|
|
350
|
+
const { err } = await import("./types");
|
|
351
|
+
const result = err<number>("something went wrong");
|
|
352
|
+
expect(result.ok).toBe(false);
|
|
353
|
+
if (!result.ok) {
|
|
354
|
+
expect(result.error).toBe("something went wrong");
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fff - Fast File Finder
|
|
3
|
+
*
|
|
4
|
+
* High-performance fuzzy file finder for Bun, powered by Rust.
|
|
5
|
+
* Perfect for LLM agent tools that need to search through codebases.
|
|
6
|
+
*
|
|
7
|
+
* Each `FileFinder` instance is backed by an independent native file picker.
|
|
8
|
+
* Create as many as you need and destroy them when done.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { FileFinder } from "fff";
|
|
13
|
+
*
|
|
14
|
+
* // Create a file finder instance
|
|
15
|
+
* const result = FileFinder.create({ basePath: "/path/to/project" });
|
|
16
|
+
* if (!result.ok) {
|
|
17
|
+
* console.error(result.error);
|
|
18
|
+
* process.exit(1);
|
|
19
|
+
* }
|
|
20
|
+
* const finder = result.value;
|
|
21
|
+
*
|
|
22
|
+
* // Wait for initial scan
|
|
23
|
+
* finder.waitForScan(5000);
|
|
24
|
+
*
|
|
25
|
+
* // Search for files
|
|
26
|
+
* const search = finder.search("main.ts");
|
|
27
|
+
* if (search.ok) {
|
|
28
|
+
* for (const item of search.value.items) {
|
|
29
|
+
* console.log(item.relativePath);
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* // Track file access (for frecency)
|
|
34
|
+
* finder.trackAccess("/path/to/project/src/main.ts");
|
|
35
|
+
*
|
|
36
|
+
* // Cleanup when done
|
|
37
|
+
* finder.destroy();
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @packageDocumentation
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
// Main API
|
|
44
|
+
export { FileFinder } from "./finder";
|
|
45
|
+
|
|
46
|
+
// Types
|
|
47
|
+
export type {
|
|
48
|
+
Result,
|
|
49
|
+
InitOptions,
|
|
50
|
+
SearchOptions,
|
|
51
|
+
FileItem,
|
|
52
|
+
Score,
|
|
53
|
+
Location,
|
|
54
|
+
SearchResult,
|
|
55
|
+
ScanProgress,
|
|
56
|
+
HealthCheck,
|
|
57
|
+
DbHealth,
|
|
58
|
+
GrepMode,
|
|
59
|
+
GrepOptions,
|
|
60
|
+
GrepMatch,
|
|
61
|
+
GrepResult,
|
|
62
|
+
GrepCursor,
|
|
63
|
+
} from "./types";
|
|
64
|
+
|
|
65
|
+
// Result helpers
|
|
66
|
+
export { ok, err } from "./types";
|
|
67
|
+
|
|
68
|
+
// Binary management (for CLI tools)
|
|
69
|
+
export {
|
|
70
|
+
downloadBinary,
|
|
71
|
+
ensureBinary,
|
|
72
|
+
binaryExists,
|
|
73
|
+
getBinaryPath,
|
|
74
|
+
findBinary,
|
|
75
|
+
} from "./download";
|
|
76
|
+
|
|
77
|
+
// Platform utilities
|
|
78
|
+
export {
|
|
79
|
+
getTriple,
|
|
80
|
+
getLibExtension,
|
|
81
|
+
getLibFilename,
|
|
82
|
+
getNpmPackageName,
|
|
83
|
+
} from "./platform";
|
package/src/platform.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform detection utilities for downloading the correct binary
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get the platform triple (e.g., "x86_64-unknown-linux-gnu")
|
|
9
|
+
*/
|
|
10
|
+
export function getTriple(): string {
|
|
11
|
+
const platform = process.platform;
|
|
12
|
+
const arch = process.arch;
|
|
13
|
+
|
|
14
|
+
let osName: string;
|
|
15
|
+
if (platform === "darwin") {
|
|
16
|
+
osName = "apple-darwin";
|
|
17
|
+
} else if (platform === "linux") {
|
|
18
|
+
osName = detectLinuxLibc();
|
|
19
|
+
} else if (platform === "win32") {
|
|
20
|
+
osName = "pc-windows-msvc";
|
|
21
|
+
} else {
|
|
22
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const archName = normalizeArch(arch);
|
|
26
|
+
return `${archName}-${osName}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detect whether we're on musl or glibc Linux
|
|
31
|
+
*/
|
|
32
|
+
function detectLinuxLibc(): string {
|
|
33
|
+
try {
|
|
34
|
+
const lddOutput = execSync("ldd --version 2>&1", {
|
|
35
|
+
encoding: "utf-8",
|
|
36
|
+
timeout: 5000,
|
|
37
|
+
});
|
|
38
|
+
if (lddOutput.toLowerCase().includes("musl")) {
|
|
39
|
+
return "unknown-linux-musl";
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// ldd failed, assume glibc
|
|
43
|
+
}
|
|
44
|
+
return "unknown-linux-gnu";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Normalize architecture name to Rust target format
|
|
49
|
+
*/
|
|
50
|
+
function normalizeArch(arch: string): string {
|
|
51
|
+
switch (arch) {
|
|
52
|
+
case "x64":
|
|
53
|
+
case "amd64":
|
|
54
|
+
return "x86_64";
|
|
55
|
+
case "arm64":
|
|
56
|
+
return "aarch64";
|
|
57
|
+
case "arm":
|
|
58
|
+
return "arm";
|
|
59
|
+
default:
|
|
60
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the library file extension for the current platform
|
|
66
|
+
*/
|
|
67
|
+
export function getLibExtension(): "dylib" | "so" | "dll" {
|
|
68
|
+
switch (process.platform) {
|
|
69
|
+
case "darwin":
|
|
70
|
+
return "dylib";
|
|
71
|
+
case "win32":
|
|
72
|
+
return "dll";
|
|
73
|
+
default:
|
|
74
|
+
return "so";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get the library filename prefix (empty on Windows)
|
|
80
|
+
*/
|
|
81
|
+
export function getLibPrefix(): string {
|
|
82
|
+
return process.platform === "win32" ? "" : "lib";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the full library filename for the current platform
|
|
87
|
+
*/
|
|
88
|
+
export function getLibFilename(): string {
|
|
89
|
+
const prefix = getLibPrefix();
|
|
90
|
+
const ext = getLibExtension();
|
|
91
|
+
return `${prefix}fff_c.${ext}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Map from Rust target triple to npm platform package name
|
|
96
|
+
*/
|
|
97
|
+
const TRIPLE_TO_NPM_PACKAGE: Record<string, string> = {
|
|
98
|
+
"aarch64-apple-darwin": "@ff-labs/fff-bun-darwin-arm64",
|
|
99
|
+
"x86_64-apple-darwin": "@ff-labs/fff-bun-darwin-x64",
|
|
100
|
+
"x86_64-unknown-linux-gnu": "@ff-labs/fff-bun-linux-x64-gnu",
|
|
101
|
+
"aarch64-unknown-linux-gnu": "@ff-labs/fff-bun-linux-arm64-gnu",
|
|
102
|
+
"x86_64-unknown-linux-musl": "@ff-labs/fff-bun-linux-x64-musl",
|
|
103
|
+
"aarch64-unknown-linux-musl": "@ff-labs/fff-bun-linux-arm64-musl",
|
|
104
|
+
"x86_64-pc-windows-msvc": "@ff-labs/fff-bun-win32-x64",
|
|
105
|
+
"aarch64-pc-windows-msvc": "@ff-labs/fff-bun-win32-arm64",
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get the npm package name for the current platform's native binary.
|
|
110
|
+
*
|
|
111
|
+
* @returns Package name like "@ff-labs/fff-bun-darwin-arm64"
|
|
112
|
+
* @throws If the current platform is not supported
|
|
113
|
+
*/
|
|
114
|
+
export function getNpmPackageName(): string {
|
|
115
|
+
const triple = getTriple();
|
|
116
|
+
const packageName = TRIPLE_TO_NPM_PACKAGE[triple];
|
|
117
|
+
if (!packageName) {
|
|
118
|
+
throw new Error(`No npm package available for platform: ${triple}`);
|
|
119
|
+
}
|
|
120
|
+
return packageName;
|
|
121
|
+
}
|