@mh-alikhani/bunready 0.3.1 → 0.3.2
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/CHANGELOG.md +17 -1
- package/package.json +2 -1
- package/src/scanner/sources.ts +40 -11
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
Nothing yet.
|
|
11
11
|
|
|
12
|
+
## [0.3.2] - 2026-09-20
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Source files are read with bounded concurrency instead of one at a time. A scan
|
|
17
|
+
of 800 files went from a 200.9ms median to 86.2ms on Windows (un run bench),
|
|
18
|
+
with identical output.
|
|
19
|
+
|
|
20
|
+
### Notes
|
|
21
|
+
|
|
22
|
+
- Measured and rejected: --bytecode cannot compile the entry point because it
|
|
23
|
+
uses top-level wait, --minify produces a byte-identical 82.2MB binary (the
|
|
24
|
+
size is the Bun runtime), and deferring the scanner behind dynamic imports moved
|
|
25
|
+
the cost into the scan path without a repeatable win.
|
|
26
|
+
|
|
12
27
|
## [0.3.1] - 2026-09-19
|
|
13
28
|
|
|
14
29
|
### Fixed
|
|
@@ -201,7 +216,8 @@ Nothing yet.
|
|
|
201
216
|
and that claim needs a primary source. Until then the rule reports what the
|
|
202
217
|
repository imports and cites the compatibility table.
|
|
203
218
|
|
|
204
|
-
[Unreleased]: https://github.com/MHAlikhani/bunready/compare/v0.3.
|
|
219
|
+
[Unreleased]: https://github.com/MHAlikhani/bunready/compare/v0.3.2...HEAD
|
|
220
|
+
[0.3.2]: https://github.com/MHAlikhani/bunready/releases/tag/v0.3.2
|
|
205
221
|
[0.3.1]: https://github.com/MHAlikhani/bunready/releases/tag/v0.3.1
|
|
206
222
|
[0.3.0]: https://github.com/MHAlikhani/bunready/releases/tag/v0.3.0
|
|
207
223
|
[0.2.0]: https://github.com/MHAlikhani/bunready/releases/tag/v0.2.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mh-alikhani/bunready",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Bun-readiness scanner: one command that shows what will break before you move a Node/TS repo to Bun.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"check": "bun run typecheck && bun run lint && bun run test",
|
|
65
65
|
"og": "bun run scripts/generate-og-image.ts",
|
|
66
66
|
"smoke": "bun run scripts/smoke-real.ts",
|
|
67
|
+
"bench": "bun run scripts/bench.ts",
|
|
67
68
|
"prepare": "simple-git-hooks"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
package/src/scanner/sources.ts
CHANGED
|
@@ -301,8 +301,41 @@ export interface ScanSourcesOptions {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
/**
|
|
304
|
-
*
|
|
305
|
-
*
|
|
304
|
+
* Bounded-concurrency reads.
|
|
305
|
+
*
|
|
306
|
+
* Files are independent, so the ordered part (the walk) and the slow part (the
|
|
307
|
+
* reads) are separated: the walk decides the order, the pool does the waiting.
|
|
308
|
+
* Results keep their walk order because each file lands in its own slot.
|
|
309
|
+
*/
|
|
310
|
+
const MAX_READ_CONCURRENCY = 16;
|
|
311
|
+
|
|
312
|
+
async function readSourceFiles(paths: readonly string[], fs: FileSystem): Promise<SourceFile[]> {
|
|
313
|
+
const slots = new Array<SourceFile | undefined>(paths.length);
|
|
314
|
+
let cursor = 0;
|
|
315
|
+
|
|
316
|
+
const worker = async (): Promise<void> => {
|
|
317
|
+
while (cursor < paths.length) {
|
|
318
|
+
const index = cursor;
|
|
319
|
+
cursor += 1;
|
|
320
|
+
const path = paths[index];
|
|
321
|
+
if (path === undefined) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const outcome = await fs.readTextFile(path);
|
|
325
|
+
if (outcome.kind === "text") {
|
|
326
|
+
slots[index] = { path, imports: extractImports(outcome.text) };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
await Promise.all(Array.from({ length: Math.min(MAX_READ_CONCURRENCY, paths.length) }, worker));
|
|
332
|
+
return slots.filter((file): file is SourceFile => file !== undefined);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Walk the target's own source, breadth first and sorted, then read it. Reading
|
|
337
|
+
* a repository of hundreds of files one at a time was the slowest thing a scan
|
|
338
|
+
* did, and the files have no dependency on each other.
|
|
306
339
|
*/
|
|
307
340
|
export async function scanSources(
|
|
308
341
|
dir: string,
|
|
@@ -313,7 +346,7 @@ export async function scanSources(
|
|
|
313
346
|
const excludePaths = options.excludePaths ?? [];
|
|
314
347
|
const ignored = new Set<string>(IGNORED_DIRECTORIES);
|
|
315
348
|
const queue: string[] = [dir];
|
|
316
|
-
const
|
|
349
|
+
const candidates: string[] = [];
|
|
317
350
|
let truncated = false;
|
|
318
351
|
|
|
319
352
|
while (queue.length > 0) {
|
|
@@ -322,8 +355,7 @@ export async function scanSources(
|
|
|
322
355
|
break;
|
|
323
356
|
}
|
|
324
357
|
|
|
325
|
-
const
|
|
326
|
-
for (const entry of entries) {
|
|
358
|
+
for (const entry of await fs.listDirectory(current)) {
|
|
327
359
|
if (entry.isDirectory) {
|
|
328
360
|
if (!ignored.has(entry.name)) {
|
|
329
361
|
queue.push(join(current, entry.name));
|
|
@@ -333,7 +365,7 @@ export async function scanSources(
|
|
|
333
365
|
if (!hasSourceExtension(entry.name)) {
|
|
334
366
|
continue;
|
|
335
367
|
}
|
|
336
|
-
if (
|
|
368
|
+
if (candidates.length >= maxFiles) {
|
|
337
369
|
truncated = true;
|
|
338
370
|
continue;
|
|
339
371
|
}
|
|
@@ -341,11 +373,7 @@ export async function scanSources(
|
|
|
341
373
|
if (excludePaths.some((fragment) => path.includes(fragment))) {
|
|
342
374
|
continue;
|
|
343
375
|
}
|
|
344
|
-
|
|
345
|
-
if (outcome.kind !== "text") {
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
files.push({ path: path.replace(/\\/g, "/"), imports: extractImports(outcome.text) });
|
|
376
|
+
candidates.push(path);
|
|
349
377
|
}
|
|
350
378
|
|
|
351
379
|
if (truncated) {
|
|
@@ -353,5 +381,6 @@ export async function scanSources(
|
|
|
353
381
|
}
|
|
354
382
|
}
|
|
355
383
|
|
|
384
|
+
const files = await readSourceFiles(candidates, fs);
|
|
356
385
|
return { files, filesScanned: files.length, truncated };
|
|
357
386
|
}
|