bun-ready 0.2.0 → 0.2.5
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 +65 -5
- package/dist/cli.js +501 -57
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ bun-ready scan .
|
|
|
23
23
|
|
|
24
24
|
## Usage
|
|
25
25
|
```bash
|
|
26
|
-
bun-ready scan <path> [--format md|json] [--out <file>] [--no-install] [--no-test] [--verbose]
|
|
26
|
+
bun-ready scan <path> [--format md|json] [--out <file>] [--no-install] [--no-test] [--verbose] [--detailed]
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
## Examples:
|
|
@@ -32,6 +32,7 @@ bun-ready scan .
|
|
|
32
32
|
bun-ready scan . --out bun-ready.md
|
|
33
33
|
bun-ready scan ./packages/api --format json
|
|
34
34
|
bun-ready scan . --no-install --no-test
|
|
35
|
+
bun-ready scan . --detailed
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
## Exit codes
|
|
@@ -71,6 +72,7 @@ You can create a `bun-ready.config.json` file in your repository root to customi
|
|
|
71
72
|
| `ignoreFindings` | Array of finding IDs to ignore | `[]` |
|
|
72
73
|
| `nativeAddonAllowlist` | Packages to exclude from native addon checks | `[]` |
|
|
73
74
|
| `failOn` | When to return non-zero exit code | `"red"` |
|
|
75
|
+
| `detailed` | Enable detailed package usage analysis | `false` |
|
|
74
76
|
|
|
75
77
|
### New CLI Flags
|
|
76
78
|
|
|
@@ -80,10 +82,68 @@ You can create a `bun-ready.config.json` file in your repository root to customi
|
|
|
80
82
|
- `all`: Scan root and all workspace packages (default)
|
|
81
83
|
|
|
82
84
|
`--fail-on green|yellow|red`
|
|
83
|
-
- Controls when bun-ready exits with a failure code
|
|
84
|
-
- `green`: Fail on anything not green (exit 3)
|
|
85
|
-
- `yellow`: Fail on red only (exit 3), yellow passes (exit 0)
|
|
86
|
-
- `red`: Default behavior - green=0, yellow=2, red=3
|
|
85
|
+
- Controls when bun-ready exits with a failure code
|
|
86
|
+
- `green`: Fail on anything not green (exit 3)
|
|
87
|
+
- `yellow`: Fail on red only (exit 3), yellow passes (exit 0)
|
|
88
|
+
- `red`: Default behavior - green=0, yellow=2, red=3
|
|
89
|
+
|
|
90
|
+
`--detailed`
|
|
91
|
+
- Enables detailed package usage analysis
|
|
92
|
+
- Shows which packages are used in which files
|
|
93
|
+
- Provides file-by-file breakdown of imports
|
|
94
|
+
- Output is written to `bun-ready-detailed.md` instead of `bun-ready.md`
|
|
95
|
+
- Requires scanning of all `.ts`, `.js`, `.tsx`, `.jsx` files in the project
|
|
96
|
+
- **Note:** This operation is slower as it needs to read and parse all source files
|
|
97
|
+
|
|
98
|
+
## Detailed Reports
|
|
99
|
+
|
|
100
|
+
When using the `--detailed` flag, bun-ready provides comprehensive package usage information:
|
|
101
|
+
|
|
102
|
+
### What it analyzes:
|
|
103
|
+
- All source files with extensions: `.ts`, `.js`, `.tsx`, `.jsx`, `.mts`, `.mjs`
|
|
104
|
+
- Import patterns supported:
|
|
105
|
+
- ES6 imports: `import ... from 'package-name'`
|
|
106
|
+
- Namespace imports: `import * as name from 'package-name'`
|
|
107
|
+
- Dynamic imports: `import('package-name')`
|
|
108
|
+
- CommonJS requires: `require('package-name')`
|
|
109
|
+
- Local imports (starting with `./` or `../`) are ignored
|
|
110
|
+
- Skips `node_modules` and hidden directories
|
|
111
|
+
|
|
112
|
+
### Output format:
|
|
113
|
+
|
|
114
|
+
The detailed report shows:
|
|
115
|
+
1. **Package Summary** - Total files analyzed and packages used
|
|
116
|
+
2. **Per-package usage** - For each package in your dependencies:
|
|
117
|
+
- How many files import it
|
|
118
|
+
- List of all file paths where it's used
|
|
119
|
+
3. **Regular findings** - All migration risk findings from standard analysis
|
|
120
|
+
|
|
121
|
+
### Example:
|
|
122
|
+
```bash
|
|
123
|
+
bun-ready scan . --detailed
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This generates `bun-ready-detailed.md` with sections like:
|
|
127
|
+
|
|
128
|
+
```markdown
|
|
129
|
+
### @nestjs/common (15 files)
|
|
130
|
+
- src/main.ts
|
|
131
|
+
- src/app.module.ts
|
|
132
|
+
- src/auth/auth.service.ts
|
|
133
|
+
...
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Configuration:
|
|
137
|
+
|
|
138
|
+
You can also enable detailed reports via config file:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"detailed": true
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
When `detailed` is set in config, it acts as if `--detailed` was passed, unless overridden by CLI flags.
|
|
87
147
|
|
|
88
148
|
## How Scoring Works
|
|
89
149
|
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, {
|
|
5
|
+
get: all[name],
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
set: (newValue) => all[name] = () => newValue
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
9
12
|
|
|
10
13
|
// src/spawn.ts
|
|
11
14
|
import { spawn } from "node:child_process";
|
|
@@ -18,8 +21,7 @@ var runNodeSpawn = async (cmd, args, cwd) => {
|
|
|
18
21
|
child.stderr.on("data", (d) => err += String(d));
|
|
19
22
|
child.on("close", (code) => resolve({ code: code ?? 1, stdout: out, stderr: err }));
|
|
20
23
|
});
|
|
21
|
-
}
|
|
22
|
-
var runBunSpawn = async (cmd, args, cwd) => {
|
|
24
|
+
}, runBunSpawn = async (cmd, args, cwd) => {
|
|
23
25
|
const BunAny = globalThis;
|
|
24
26
|
const bun = BunAny.Bun;
|
|
25
27
|
if (!bun)
|
|
@@ -46,10 +48,53 @@ var runBunSpawn = async (cmd, args, cwd) => {
|
|
|
46
48
|
};
|
|
47
49
|
const [stdout, stderr, code] = await Promise.all([readAll(proc.stdout), readAll(proc.stderr), proc.exited]);
|
|
48
50
|
return { code, stdout, stderr };
|
|
49
|
-
}
|
|
50
|
-
var exec = async (cmd, args, cwd) => {
|
|
51
|
+
}, exec = async (cmd, args, cwd) => {
|
|
51
52
|
return await runBunSpawn(cmd, args, cwd);
|
|
52
53
|
};
|
|
54
|
+
var init_spawn = () => {};
|
|
55
|
+
|
|
56
|
+
// src/bun_check.ts
|
|
57
|
+
var exports_bun_check = {};
|
|
58
|
+
__export(exports_bun_check, {
|
|
59
|
+
checkBunAvailable: () => checkBunAvailable
|
|
60
|
+
});
|
|
61
|
+
async function checkBunAvailable() {
|
|
62
|
+
try {
|
|
63
|
+
const res = await exec("bun", ["--version"], process.cwd());
|
|
64
|
+
if (res.code === 0 && res.stdout.includes("bun")) {
|
|
65
|
+
return { available: true };
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
available: false,
|
|
69
|
+
error: `Bun command returned unexpected output (exit ${res.code})`
|
|
70
|
+
};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
73
|
+
if (msg.includes("ENOENT") || msg.includes("spawn bun ENOENT")) {
|
|
74
|
+
return {
|
|
75
|
+
available: false,
|
|
76
|
+
error: "Bun is not installed. Please install Bun from https://bun.sh or use --no-install and --no-test flags."
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
available: false,
|
|
81
|
+
error: `Failed to check Bun availability: ${msg}`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
var init_bun_check = __esm(() => {
|
|
86
|
+
init_spawn();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// src/cli.ts
|
|
90
|
+
import { promises as fs4 } from "node:fs";
|
|
91
|
+
import path6 from "node:path";
|
|
92
|
+
|
|
93
|
+
// src/analyze.ts
|
|
94
|
+
init_spawn();
|
|
95
|
+
import path5 from "node:path";
|
|
96
|
+
import os from "node:os";
|
|
97
|
+
import { promises as fs3 } from "node:fs";
|
|
53
98
|
|
|
54
99
|
// src/util.ts
|
|
55
100
|
import { promises as fs } from "node:fs";
|
|
@@ -399,7 +444,15 @@ var detectNativeAddonRiskV2 = (repo, config) => {
|
|
|
399
444
|
const suspects = names.filter((n) => {
|
|
400
445
|
if (allowlist.includes(n))
|
|
401
446
|
return false;
|
|
402
|
-
|
|
447
|
+
const inList = NATIVE_SUSPECTS_V2.includes(n);
|
|
448
|
+
if (inList)
|
|
449
|
+
return true;
|
|
450
|
+
const keywords = ["@napi-rs/", "napi-rs", "node-napi", "neon", "node-gyp", "prebuild", "ffi", "bindings", "native", "native-module"];
|
|
451
|
+
const keywordMatch = includesAny(n, keywords);
|
|
452
|
+
if (keywordMatch) {
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
return false;
|
|
403
456
|
});
|
|
404
457
|
const scriptNames = Object.keys(repo.scripts);
|
|
405
458
|
const hasNodeGypRebuild = scriptNames.some((k) => {
|
|
@@ -435,6 +488,173 @@ var summarizeSeverity = (findings, installOk, testOk) => {
|
|
|
435
488
|
sev = "red";
|
|
436
489
|
return sev;
|
|
437
490
|
};
|
|
491
|
+
var calculatePackageStats = (pkg, findings) => {
|
|
492
|
+
const dependencies = pkg.dependencies || {};
|
|
493
|
+
const devDependencies = pkg.devDependencies || {};
|
|
494
|
+
const riskyPackageNames = new Set;
|
|
495
|
+
for (const finding of findings) {
|
|
496
|
+
for (const detail of finding.details) {
|
|
497
|
+
const match = detail.match(/^([a-zA-Z0-9_@\/\.\-]+)/);
|
|
498
|
+
if (match && match[1]) {
|
|
499
|
+
const fullPkg = match[1];
|
|
500
|
+
const pkgName = fullPkg.split(/[@:]/)[0];
|
|
501
|
+
if (pkgName) {
|
|
502
|
+
riskyPackageNames.add(pkgName);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
let cleanDependencies = 0;
|
|
508
|
+
let riskyDependencies = 0;
|
|
509
|
+
let cleanDevDependencies = 0;
|
|
510
|
+
let riskyDevDependencies = 0;
|
|
511
|
+
for (const depName of Object.keys(dependencies)) {
|
|
512
|
+
if (riskyPackageNames.has(depName)) {
|
|
513
|
+
riskyDependencies++;
|
|
514
|
+
} else {
|
|
515
|
+
cleanDependencies++;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
for (const depName of Object.keys(devDependencies)) {
|
|
519
|
+
if (riskyPackageNames.has(depName)) {
|
|
520
|
+
riskyDevDependencies++;
|
|
521
|
+
} else {
|
|
522
|
+
cleanDevDependencies++;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
totalDependencies: Object.keys(dependencies).length,
|
|
527
|
+
totalDevDependencies: Object.keys(devDependencies).length,
|
|
528
|
+
cleanDependencies,
|
|
529
|
+
cleanDevDependencies,
|
|
530
|
+
riskyDependencies,
|
|
531
|
+
riskyDevDependencies
|
|
532
|
+
};
|
|
533
|
+
};
|
|
534
|
+
var calculateFindingsSummary = (findings) => {
|
|
535
|
+
let green = 0;
|
|
536
|
+
let yellow = 0;
|
|
537
|
+
let red = 0;
|
|
538
|
+
for (const finding of findings) {
|
|
539
|
+
if (finding.severity === "green") {
|
|
540
|
+
green++;
|
|
541
|
+
} else if (finding.severity === "yellow") {
|
|
542
|
+
yellow++;
|
|
543
|
+
} else if (finding.severity === "red") {
|
|
544
|
+
red++;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
green,
|
|
549
|
+
yellow,
|
|
550
|
+
red,
|
|
551
|
+
total: green + yellow + red
|
|
552
|
+
};
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// src/usage_analyzer.ts
|
|
556
|
+
import { promises as fs2 } from "node:fs";
|
|
557
|
+
import path2 from "node:path";
|
|
558
|
+
var SUPPORTED_EXTENSIONS = [".ts", ".js", ".tsx", ".jsx", ".mts", ".mjs"];
|
|
559
|
+
var IMPORT_PATTERNS = [
|
|
560
|
+
/import\s+(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^./][^'"]*)['"]/g,
|
|
561
|
+
/import\s*\(\s*['"]([^./][^'"]*)['"]\s*\)/g,
|
|
562
|
+
/require\s*\(\s*['"]([^./][^'"]*)['"]\s*\)/g
|
|
563
|
+
];
|
|
564
|
+
function extractPackageNames(content) {
|
|
565
|
+
const packageSet = new Set;
|
|
566
|
+
for (const pattern of IMPORT_PATTERNS) {
|
|
567
|
+
let match;
|
|
568
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
569
|
+
const packageName = match[1];
|
|
570
|
+
if (packageName) {
|
|
571
|
+
packageSet.add(packageName);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return Array.from(packageSet);
|
|
576
|
+
}
|
|
577
|
+
async function findSourceFiles(dirPath) {
|
|
578
|
+
const files = [];
|
|
579
|
+
let entries;
|
|
580
|
+
try {
|
|
581
|
+
entries = await fs2.readdir(dirPath, { withFileTypes: true });
|
|
582
|
+
} catch (error) {
|
|
583
|
+
return files;
|
|
584
|
+
}
|
|
585
|
+
for (const entry of entries) {
|
|
586
|
+
const fullPath = path2.join(dirPath, entry.name);
|
|
587
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) {
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
if (entry.isDirectory()) {
|
|
591
|
+
const subFiles = await findSourceFiles(fullPath);
|
|
592
|
+
files.push(...subFiles);
|
|
593
|
+
} else if (entry.isFile()) {
|
|
594
|
+
const ext = path2.extname(entry.name);
|
|
595
|
+
if (SUPPORTED_EXTENSIONS.includes(ext)) {
|
|
596
|
+
files.push(fullPath);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return files;
|
|
601
|
+
}
|
|
602
|
+
function getAllPackageNames(pkg) {
|
|
603
|
+
const packageNames = new Set;
|
|
604
|
+
if (pkg.dependencies) {
|
|
605
|
+
Object.keys(pkg.dependencies).forEach((name) => packageNames.add(name));
|
|
606
|
+
}
|
|
607
|
+
if (pkg.devDependencies) {
|
|
608
|
+
Object.keys(pkg.devDependencies).forEach((name) => packageNames.add(name));
|
|
609
|
+
}
|
|
610
|
+
if (pkg.optionalDependencies) {
|
|
611
|
+
Object.keys(pkg.optionalDependencies).forEach((name) => packageNames.add(name));
|
|
612
|
+
}
|
|
613
|
+
return packageNames;
|
|
614
|
+
}
|
|
615
|
+
var analyzePackageUsageAsync = async (pkg, packagePath, includeDetails = true) => {
|
|
616
|
+
const packageNames = getAllPackageNames(pkg);
|
|
617
|
+
const totalPackages = packageNames.size;
|
|
618
|
+
const sourceFiles = await findSourceFiles(packagePath);
|
|
619
|
+
const analyzedFiles = sourceFiles.length;
|
|
620
|
+
const usageByPackage = new Map;
|
|
621
|
+
for (const pkgName of packageNames) {
|
|
622
|
+
usageByPackage.set(pkgName, {
|
|
623
|
+
packageName: pkgName,
|
|
624
|
+
fileCount: 0,
|
|
625
|
+
filePaths: []
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
for (const filePath of sourceFiles) {
|
|
629
|
+
try {
|
|
630
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
631
|
+
const importedPackages = extractPackageNames(content);
|
|
632
|
+
for (const importedPkg of importedPackages) {
|
|
633
|
+
const usage = usageByPackage.get(importedPkg);
|
|
634
|
+
if (usage) {
|
|
635
|
+
usage.fileCount++;
|
|
636
|
+
if (includeDetails) {
|
|
637
|
+
const relativePath = path2.relative(packagePath, filePath);
|
|
638
|
+
usage.filePaths.push(relativePath);
|
|
639
|
+
}
|
|
640
|
+
usageByPackage.set(importedPkg, usage);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
} catch (error) {
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (includeDetails) {
|
|
648
|
+
for (const usage of usageByPackage.values()) {
|
|
649
|
+
usage.filePaths.sort();
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
totalPackages,
|
|
654
|
+
analyzedFiles,
|
|
655
|
+
usageByPackage
|
|
656
|
+
};
|
|
657
|
+
};
|
|
438
658
|
|
|
439
659
|
// src/bun_logs.ts
|
|
440
660
|
function parseInstallLogs(logs) {
|
|
@@ -490,11 +710,11 @@ function parseInstallLogs(logs) {
|
|
|
490
710
|
}
|
|
491
711
|
|
|
492
712
|
// src/workspaces.ts
|
|
493
|
-
import
|
|
713
|
+
import path3 from "node:path";
|
|
494
714
|
import fsSync from "node:fs";
|
|
495
|
-
function globMatch(pattern,
|
|
715
|
+
function globMatch(pattern, path4) {
|
|
496
716
|
const patternParts = pattern.split("/");
|
|
497
|
-
const pathParts =
|
|
717
|
+
const pathParts = path4.split("/");
|
|
498
718
|
let patternIdx = 0;
|
|
499
719
|
let pathIdx = 0;
|
|
500
720
|
while (patternIdx < patternParts.length && pathIdx < pathParts.length) {
|
|
@@ -532,16 +752,16 @@ function discoverFromWorkspaces(rootPath, workspaces) {
|
|
|
532
752
|
const patterns = Array.isArray(workspaces) ? workspaces : workspaces.packages;
|
|
533
753
|
const packages = [];
|
|
534
754
|
for (const pattern of patterns) {
|
|
535
|
-
const patternPath =
|
|
755
|
+
const patternPath = path3.resolve(rootPath, pattern);
|
|
536
756
|
if (pattern.includes("/*") && !pattern.includes("/**")) {
|
|
537
757
|
try {
|
|
538
|
-
const baseDir =
|
|
539
|
-
const patternName =
|
|
758
|
+
const baseDir = path3.dirname(patternPath);
|
|
759
|
+
const patternName = path3.basename(patternPath);
|
|
540
760
|
const entries = fsSync.readdirSync(baseDir, { withFileTypes: true });
|
|
541
761
|
for (const entry of entries) {
|
|
542
762
|
if (entry.isDirectory() && globMatch(patternName, entry.name)) {
|
|
543
|
-
const packagePath =
|
|
544
|
-
const pkgJsonPath =
|
|
763
|
+
const packagePath = path3.join(baseDir, entry.name);
|
|
764
|
+
const pkgJsonPath = path3.join(packagePath, "package.json");
|
|
545
765
|
if (fileExistsSync(pkgJsonPath)) {
|
|
546
766
|
packages.push(packagePath);
|
|
547
767
|
}
|
|
@@ -562,7 +782,7 @@ function fileExistsSync(filePath) {
|
|
|
562
782
|
}
|
|
563
783
|
async function discoverWorkspaces(rootPath) {
|
|
564
784
|
const packages = [];
|
|
565
|
-
const rootPkgJson =
|
|
785
|
+
const rootPkgJson = path3.join(rootPath, "package.json");
|
|
566
786
|
if (!await fileExists(rootPkgJson)) {
|
|
567
787
|
return packages;
|
|
568
788
|
}
|
|
@@ -576,7 +796,7 @@ async function discoverWorkspaces(rootPath) {
|
|
|
576
796
|
}
|
|
577
797
|
const packagePaths = discoverFromWorkspaces(rootPath, workspaces);
|
|
578
798
|
for (const pkgPath of packagePaths) {
|
|
579
|
-
const pkgJsonPath =
|
|
799
|
+
const pkgJsonPath = path3.join(pkgPath, "package.json");
|
|
580
800
|
try {
|
|
581
801
|
const pkg = await readJsonFile(pkgJsonPath);
|
|
582
802
|
if (pkg.name) {
|
|
@@ -594,7 +814,7 @@ async function discoverWorkspaces(rootPath) {
|
|
|
594
814
|
return packages;
|
|
595
815
|
}
|
|
596
816
|
async function hasWorkspaces(rootPath) {
|
|
597
|
-
const rootPkgJson =
|
|
817
|
+
const rootPkgJson = path3.join(rootPath, "package.json");
|
|
598
818
|
if (!await fileExists(rootPkgJson)) {
|
|
599
819
|
return false;
|
|
600
820
|
}
|
|
@@ -603,10 +823,10 @@ async function hasWorkspaces(rootPath) {
|
|
|
603
823
|
}
|
|
604
824
|
|
|
605
825
|
// src/config.ts
|
|
606
|
-
import
|
|
826
|
+
import path4 from "node:path";
|
|
607
827
|
var CONFIG_FILE_NAME = "bun-ready.config.json";
|
|
608
828
|
async function readConfig(rootPath) {
|
|
609
|
-
const configPath =
|
|
829
|
+
const configPath = path4.join(rootPath, CONFIG_FILE_NAME);
|
|
610
830
|
if (!await fileExists(configPath)) {
|
|
611
831
|
return null;
|
|
612
832
|
}
|
|
@@ -650,13 +870,16 @@ function validateConfig(config) {
|
|
|
650
870
|
result.failOn = cfg.failOn;
|
|
651
871
|
}
|
|
652
872
|
}
|
|
873
|
+
if (typeof cfg.detailed === "boolean") {
|
|
874
|
+
result.detailed = cfg.detailed;
|
|
875
|
+
}
|
|
653
876
|
if (Object.keys(result).length === 0) {
|
|
654
877
|
return null;
|
|
655
878
|
}
|
|
656
879
|
return result;
|
|
657
880
|
}
|
|
658
881
|
function mergeConfigWithOpts(config, opts) {
|
|
659
|
-
if (!config && !opts.failOn) {
|
|
882
|
+
if (!config && !opts.failOn && opts.detailed === undefined) {
|
|
660
883
|
return null;
|
|
661
884
|
}
|
|
662
885
|
const result = {
|
|
@@ -665,49 +888,64 @@ function mergeConfigWithOpts(config, opts) {
|
|
|
665
888
|
if (opts.failOn) {
|
|
666
889
|
result.failOn = opts.failOn;
|
|
667
890
|
}
|
|
891
|
+
if (opts.detailed !== undefined) {
|
|
892
|
+
result.detailed = opts.detailed;
|
|
893
|
+
}
|
|
668
894
|
return Object.keys(result).length > 0 ? result : null;
|
|
669
895
|
}
|
|
670
896
|
|
|
671
897
|
// src/analyze.ts
|
|
672
898
|
async function readRepoInfo(packagePath) {
|
|
673
|
-
const packageJsonPath =
|
|
899
|
+
const packageJsonPath = path5.join(packagePath, "package.json");
|
|
674
900
|
const pkg = await readJsonFile(packageJsonPath);
|
|
675
901
|
const scripts = pkg.scripts ?? {};
|
|
676
902
|
const dependencies = pkg.dependencies ?? {};
|
|
677
903
|
const devDependencies = pkg.devDependencies ?? {};
|
|
678
904
|
const optionalDependencies = pkg.optionalDependencies ?? {};
|
|
679
905
|
const lockfiles = {
|
|
680
|
-
bunLock: await fileExists(
|
|
681
|
-
bunLockb: await fileExists(
|
|
682
|
-
npmLock: await fileExists(
|
|
683
|
-
yarnLock: await fileExists(
|
|
684
|
-
pnpmLock: await fileExists(
|
|
906
|
+
bunLock: await fileExists(path5.join(packagePath, "bun.lock")),
|
|
907
|
+
bunLockb: await fileExists(path5.join(packagePath, "bun.lockb")),
|
|
908
|
+
npmLock: await fileExists(path5.join(packagePath, "package-lock.json")),
|
|
909
|
+
yarnLock: await fileExists(path5.join(packagePath, "yarn.lock")),
|
|
910
|
+
pnpmLock: await fileExists(path5.join(packagePath, "pnpm-lock.yaml"))
|
|
685
911
|
};
|
|
686
912
|
return { pkg, scripts, dependencies, devDependencies, optionalDependencies, lockfiles };
|
|
687
913
|
}
|
|
688
914
|
async function copyIfExists(from, to) {
|
|
689
915
|
try {
|
|
690
|
-
await
|
|
916
|
+
await fs3.copyFile(from, to);
|
|
691
917
|
} catch {
|
|
692
918
|
return;
|
|
693
919
|
}
|
|
694
920
|
}
|
|
695
921
|
async function runBunInstallDryRun(packagePath) {
|
|
696
|
-
const
|
|
922
|
+
const { checkBunAvailable: checkBunAvailable2 } = await Promise.resolve().then(() => (init_bun_check(), exports_bun_check));
|
|
923
|
+
const bunCheck = await checkBunAvailable2();
|
|
924
|
+
if (!bunCheck.available) {
|
|
925
|
+
const skipReason = bunCheck.error;
|
|
926
|
+
return {
|
|
927
|
+
ok: false,
|
|
928
|
+
summary: `Skipped: ${skipReason}`,
|
|
929
|
+
logs: [],
|
|
930
|
+
installAnalysis: { blockedDeps: [], trustedDepsMentioned: [], notes: [] },
|
|
931
|
+
skipReason
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
const base = await fs3.mkdtemp(path5.join(os.tmpdir(), "bun-ready-"));
|
|
697
935
|
const cleanup = async () => {
|
|
698
936
|
try {
|
|
699
|
-
await
|
|
937
|
+
await fs3.rm(base, { recursive: true, force: true });
|
|
700
938
|
} catch {
|
|
701
939
|
return;
|
|
702
940
|
}
|
|
703
941
|
};
|
|
704
942
|
try {
|
|
705
|
-
await copyIfExists(
|
|
706
|
-
await copyIfExists(
|
|
707
|
-
await copyIfExists(
|
|
708
|
-
await copyIfExists(
|
|
709
|
-
await copyIfExists(
|
|
710
|
-
await copyIfExists(
|
|
943
|
+
await copyIfExists(path5.join(packagePath, "package.json"), path5.join(base, "package.json"));
|
|
944
|
+
await copyIfExists(path5.join(packagePath, "bun.lock"), path5.join(base, "bun.lock"));
|
|
945
|
+
await copyIfExists(path5.join(packagePath, "bun.lockb"), path5.join(base, "bun.lockb"));
|
|
946
|
+
await copyIfExists(path5.join(packagePath, "package-lock.json"), path5.join(base, "package-lock.json"));
|
|
947
|
+
await copyIfExists(path5.join(packagePath, "yarn.lock"), path5.join(base, "yarn.lock"));
|
|
948
|
+
await copyIfExists(path5.join(packagePath, "pnpm-lock.yaml"), path5.join(base, "pnpm-lock.yaml"));
|
|
711
949
|
const res = await exec("bun", ["install", "--dry-run"], base);
|
|
712
950
|
const combined = [...res.stdout ? res.stdout.split(`
|
|
713
951
|
`) : [], ...res.stderr ? res.stderr.split(`
|
|
@@ -726,6 +964,17 @@ function shouldRunBunTest(scripts) {
|
|
|
726
964
|
return t.toLowerCase().includes("bun test") || t.toLowerCase().trim() === "bun test";
|
|
727
965
|
}
|
|
728
966
|
async function runBunTest(packagePath) {
|
|
967
|
+
const { checkBunAvailable: checkBunAvailable2 } = await Promise.resolve().then(() => (init_bun_check(), exports_bun_check));
|
|
968
|
+
const bunCheck = await checkBunAvailable2();
|
|
969
|
+
if (!bunCheck.available) {
|
|
970
|
+
const skipReason = bunCheck.error;
|
|
971
|
+
return {
|
|
972
|
+
ok: false,
|
|
973
|
+
summary: `Skipped: ${skipReason}`,
|
|
974
|
+
logs: [],
|
|
975
|
+
skipReason
|
|
976
|
+
};
|
|
977
|
+
}
|
|
729
978
|
const res = await exec("bun", ["test"], packagePath);
|
|
730
979
|
const combined = [...res.stdout ? res.stdout.split(`
|
|
731
980
|
`) : [], ...res.stderr ? res.stderr.split(`
|
|
@@ -742,7 +991,7 @@ function filterFindings(findings, config) {
|
|
|
742
991
|
}
|
|
743
992
|
async function analyzeSinglePackage(packagePath, opts, config, pkgName) {
|
|
744
993
|
const info = await readRepoInfo(packagePath);
|
|
745
|
-
const name = pkgName || info.pkg.name ||
|
|
994
|
+
const name = pkgName || info.pkg.name || path5.basename(packagePath);
|
|
746
995
|
let findings = [
|
|
747
996
|
...detectLockfileSignals({ packageJsonPath: packagePath, lockfiles: info.lockfiles, scripts: info.scripts, dependencies: info.dependencies, devDependencies: info.devDependencies, optionalDependencies: info.optionalDependencies, hasWorkspaces: false, packageJson: info.pkg }),
|
|
748
997
|
...detectScriptRisks({ packageJsonPath: packagePath, lockfiles: info.lockfiles, scripts: info.scripts, dependencies: info.dependencies, devDependencies: info.devDependencies, optionalDependencies: info.optionalDependencies, hasWorkspaces: false, packageJson: info.pkg }),
|
|
@@ -758,7 +1007,8 @@ async function analyzeSinglePackage(packagePath, opts, config, pkgName) {
|
|
|
758
1007
|
install = {
|
|
759
1008
|
ok: installResult.ok,
|
|
760
1009
|
summary: installResult.summary,
|
|
761
|
-
logs: installResult.logs
|
|
1010
|
+
logs: installResult.logs,
|
|
1011
|
+
...installResult.skipReason !== undefined ? { skipReason: installResult.skipReason } : {}
|
|
762
1012
|
};
|
|
763
1013
|
installOk = installResult.ok;
|
|
764
1014
|
if (installResult.installAnalysis.blockedDeps.length > 0) {
|
|
@@ -794,18 +1044,28 @@ async function analyzeSinglePackage(packagePath, opts, config, pkgName) {
|
|
|
794
1044
|
test = {
|
|
795
1045
|
ok: testResult.ok,
|
|
796
1046
|
summary: testResult.summary,
|
|
797
|
-
logs: testResult.logs
|
|
1047
|
+
logs: testResult.logs,
|
|
1048
|
+
...testResult.skipReason !== undefined ? { skipReason: testResult.skipReason } : {}
|
|
798
1049
|
};
|
|
799
1050
|
testOk = testResult.ok;
|
|
800
1051
|
}
|
|
801
1052
|
const severity = summarizeSeverity(findings, installOk, testOk);
|
|
1053
|
+
const stats = calculatePackageStats(info.pkg, findings);
|
|
1054
|
+
const findingsSummary = calculateFindingsSummary(findings);
|
|
1055
|
+
let packageUsage;
|
|
1056
|
+
if (opts.detailed) {
|
|
1057
|
+
try {
|
|
1058
|
+
const usage = await analyzePackageUsageAsync(info.pkg, packagePath, true);
|
|
1059
|
+
packageUsage = usage;
|
|
1060
|
+
} catch (error) {}
|
|
1061
|
+
}
|
|
802
1062
|
const summaryLines = [];
|
|
803
1063
|
summaryLines.push(`Lockfiles: ${info.lockfiles.bunLock || info.lockfiles.bunLockb ? "bun" : "non-bun or missing"}`);
|
|
804
1064
|
summaryLines.push(`Lifecycle scripts: ${Object.keys(info.scripts).some((k) => ["postinstall", "prepare", "preinstall", "install"].includes(k)) ? "present" : "none"}`);
|
|
805
1065
|
summaryLines.push(`Native addon risk: ${findings.some((f) => f.id === "deps.native_addons") ? "yes" : "no"}`);
|
|
806
1066
|
summaryLines.push(`bun install dry-run: ${install ? install.ok ? "ok" : "failed" : "skipped"}`);
|
|
807
1067
|
summaryLines.push(`bun test: ${test ? test.ok ? "ok" : "failed" : "skipped"}`);
|
|
808
|
-
|
|
1068
|
+
const result = {
|
|
809
1069
|
name,
|
|
810
1070
|
path: packagePath,
|
|
811
1071
|
severity,
|
|
@@ -817,8 +1077,14 @@ async function analyzeSinglePackage(packagePath, opts, config, pkgName) {
|
|
|
817
1077
|
dependencies: info.dependencies,
|
|
818
1078
|
devDependencies: info.devDependencies,
|
|
819
1079
|
optionalDependencies: info.optionalDependencies,
|
|
820
|
-
lockfiles: info.lockfiles
|
|
1080
|
+
lockfiles: info.lockfiles,
|
|
1081
|
+
stats,
|
|
1082
|
+
findingsSummary
|
|
821
1083
|
};
|
|
1084
|
+
if (packageUsage !== undefined) {
|
|
1085
|
+
result.packageUsage = packageUsage;
|
|
1086
|
+
}
|
|
1087
|
+
return result;
|
|
822
1088
|
}
|
|
823
1089
|
function aggregateSeverity(packages, overallSeverity) {
|
|
824
1090
|
if (overallSeverity === "red")
|
|
@@ -837,7 +1103,7 @@ function aggregateSeverity(packages, overallSeverity) {
|
|
|
837
1103
|
}
|
|
838
1104
|
async function analyzeRepoOverall(opts) {
|
|
839
1105
|
const repoPath = normalizeRepoPath(opts.repoPath);
|
|
840
|
-
const packageJsonPath =
|
|
1106
|
+
const packageJsonPath = path5.join(repoPath, "package.json");
|
|
841
1107
|
const hasPkg = await fileExists(packageJsonPath);
|
|
842
1108
|
const config = await readConfig(repoPath);
|
|
843
1109
|
if (!hasPkg) {
|
|
@@ -951,6 +1217,27 @@ var badge = (s) => {
|
|
|
951
1217
|
return "\uD83D\uDFE1 YELLOW";
|
|
952
1218
|
return "\uD83D\uDD34 RED";
|
|
953
1219
|
};
|
|
1220
|
+
var getReadinessMessage = (severity, hasRedFindings) => {
|
|
1221
|
+
if (severity === "green" && !hasRedFindings) {
|
|
1222
|
+
return "✅ Вітаю, ви готові до переходу на Bun!";
|
|
1223
|
+
}
|
|
1224
|
+
if (severity === "yellow") {
|
|
1225
|
+
return "⚠️ Нажаль ви не готові до переходу на Bun, але це можливо з деякими змінами";
|
|
1226
|
+
}
|
|
1227
|
+
return "❌ Нажаль ви не готові до переходу на Bun через критичні проблеми";
|
|
1228
|
+
};
|
|
1229
|
+
var formatFindingsTable = (summary) => {
|
|
1230
|
+
const lines = [];
|
|
1231
|
+
lines.push(`## Findings Summary`);
|
|
1232
|
+
lines.push(`| Status | Count |`);
|
|
1233
|
+
lines.push(`|--------|-------|`);
|
|
1234
|
+
lines.push(`| \uD83D\uDFE2 Green | ${summary.green} |`);
|
|
1235
|
+
lines.push(`| \uD83D\uDFE1 Yellow | ${summary.yellow} |`);
|
|
1236
|
+
lines.push(`| \uD83D\uDD34 Red | ${summary.red} |`);
|
|
1237
|
+
lines.push(`| **Total** | **${summary.total}** |`);
|
|
1238
|
+
return lines.join(`
|
|
1239
|
+
`);
|
|
1240
|
+
};
|
|
954
1241
|
var getTopFindings = (pkg, count = 3) => {
|
|
955
1242
|
const sorted = [...pkg.findings].sort((a, b) => {
|
|
956
1243
|
const severityOrder = { red: 0, yellow: 1, green: 2 };
|
|
@@ -963,14 +1250,44 @@ var getTopFindings = (pkg, count = 3) => {
|
|
|
963
1250
|
};
|
|
964
1251
|
var packageRow = (pkg) => {
|
|
965
1252
|
const name = pkg.name;
|
|
966
|
-
const
|
|
1253
|
+
const path6 = pkg.path.replace(/\\/g, "/");
|
|
967
1254
|
const severity = badge(pkg.severity);
|
|
968
1255
|
const topFindings = getTopFindings(pkg, 2).join(", ") || "No issues";
|
|
969
|
-
return `| ${name} | \`${
|
|
1256
|
+
return `| ${name} | \`${path6}\` | ${severity} | ${topFindings} |`;
|
|
1257
|
+
};
|
|
1258
|
+
var formatPackageStats = (pkg) => {
|
|
1259
|
+
const lines = [];
|
|
1260
|
+
if (pkg.stats) {
|
|
1261
|
+
lines.push(`- Total dependencies: ${pkg.stats.totalDependencies}`);
|
|
1262
|
+
lines.push(`- Total devDependencies: ${pkg.stats.totalDevDependencies}`);
|
|
1263
|
+
lines.push(`- Clean dependencies: ${pkg.stats.cleanDependencies}`);
|
|
1264
|
+
lines.push(`- Clean devDependencies: ${pkg.stats.cleanDevDependencies}`);
|
|
1265
|
+
lines.push(`- Dependencies with findings: ${pkg.stats.riskyDependencies}`);
|
|
1266
|
+
lines.push(`- DevDependencies with findings: ${pkg.stats.riskyDevDependencies}`);
|
|
1267
|
+
}
|
|
1268
|
+
if (pkg.packageUsage) {
|
|
1269
|
+
lines.push(`- **Total files analyzed**: ${pkg.packageUsage.analyzedFiles}`);
|
|
1270
|
+
const usedPackages = Array.from(pkg.packageUsage.usageByPackage.values()).filter((u) => u.fileCount > 0).length;
|
|
1271
|
+
lines.push(`- **Packages used in code**: ${usedPackages}`);
|
|
1272
|
+
}
|
|
1273
|
+
return lines;
|
|
970
1274
|
};
|
|
971
1275
|
function renderMarkdown(r) {
|
|
972
1276
|
const lines = [];
|
|
973
|
-
|
|
1277
|
+
const bunVersion = process.version;
|
|
1278
|
+
const hasRedFindings = r.findings.some((f) => f.severity === "red");
|
|
1279
|
+
const readinessMessage = getReadinessMessage(r.severity, hasRedFindings);
|
|
1280
|
+
lines.push(`# bun-ready report - Tested with Bun ${bunVersion}`);
|
|
1281
|
+
lines.push(``);
|
|
1282
|
+
lines.push(readinessMessage);
|
|
1283
|
+
lines.push(``);
|
|
1284
|
+
const rootFindingsSummary = {
|
|
1285
|
+
green: r.findings.filter((f) => f.severity === "green").length,
|
|
1286
|
+
yellow: r.findings.filter((f) => f.severity === "yellow").length,
|
|
1287
|
+
red: r.findings.filter((f) => f.severity === "red").length,
|
|
1288
|
+
total: r.findings.length
|
|
1289
|
+
};
|
|
1290
|
+
lines.push(formatFindingsTable(rootFindingsSummary));
|
|
974
1291
|
lines.push(``);
|
|
975
1292
|
lines.push(`**Overall:** ${badge(r.severity)}`);
|
|
976
1293
|
lines.push(``);
|
|
@@ -1049,6 +1366,13 @@ function renderMarkdown(r) {
|
|
|
1049
1366
|
}
|
|
1050
1367
|
lines.push(``);
|
|
1051
1368
|
}
|
|
1369
|
+
const rootPkgForStats = r.packages?.find((p) => p.path === r.repo.packageJsonPath);
|
|
1370
|
+
if (rootPkgForStats && rootPkgForStats.stats) {
|
|
1371
|
+
lines.push(`## Package Summary`);
|
|
1372
|
+
for (const l of formatPackageStats(rootPkgForStats))
|
|
1373
|
+
lines.push(l);
|
|
1374
|
+
lines.push(``);
|
|
1375
|
+
}
|
|
1052
1376
|
lines.push(`## Root Findings`);
|
|
1053
1377
|
if (r.findings.length === 0) {
|
|
1054
1378
|
lines.push(`No findings for root package.`);
|
|
@@ -1079,6 +1403,12 @@ function renderMarkdown(r) {
|
|
|
1079
1403
|
for (const l of pkg.summaryLines)
|
|
1080
1404
|
lines.push(`- ${l}`);
|
|
1081
1405
|
lines.push(``);
|
|
1406
|
+
if (pkg.stats) {
|
|
1407
|
+
lines.push(`**Package Summary**`);
|
|
1408
|
+
for (const l of formatPackageStats(pkg))
|
|
1409
|
+
lines.push(l);
|
|
1410
|
+
lines.push(``);
|
|
1411
|
+
}
|
|
1082
1412
|
if (pkg.install) {
|
|
1083
1413
|
lines.push(`**bun install (dry-run):** ${pkg.install.ok ? "ok" : "failed"}`);
|
|
1084
1414
|
if (pkg.install.logs.length > 0 && pkg.install.logs.length < 10) {
|
|
@@ -1124,6 +1454,90 @@ function renderMarkdown(r) {
|
|
|
1124
1454
|
return lines.join(`
|
|
1125
1455
|
`);
|
|
1126
1456
|
}
|
|
1457
|
+
var renderDetailedReport = (r) => {
|
|
1458
|
+
const lines = [];
|
|
1459
|
+
const bunVersion = process.version;
|
|
1460
|
+
const hasRedFindings = r.findings.some((f) => f.severity === "red");
|
|
1461
|
+
const readinessMessage = getReadinessMessage(r.severity, hasRedFindings);
|
|
1462
|
+
lines.push(`# bun-ready detailed report - Tested with Bun ${bunVersion}`);
|
|
1463
|
+
lines.push(``);
|
|
1464
|
+
lines.push(readinessMessage);
|
|
1465
|
+
lines.push(``);
|
|
1466
|
+
const rootFindingsSummary = {
|
|
1467
|
+
green: r.findings.filter((f) => f.severity === "green").length,
|
|
1468
|
+
yellow: r.findings.filter((f) => f.severity === "yellow").length,
|
|
1469
|
+
red: r.findings.filter((f) => f.severity === "red").length,
|
|
1470
|
+
total: r.findings.length
|
|
1471
|
+
};
|
|
1472
|
+
lines.push(formatFindingsTable(rootFindingsSummary));
|
|
1473
|
+
lines.push(``);
|
|
1474
|
+
lines.push(`**Overall:** ${badge(r.severity)}`);
|
|
1475
|
+
lines.push(``);
|
|
1476
|
+
lines.push(`## Detailed Package Usage`);
|
|
1477
|
+
lines.push(``);
|
|
1478
|
+
let hasUsageInfo = false;
|
|
1479
|
+
if (r.packages && r.packages.length > 0) {
|
|
1480
|
+
const sortedPackages = stableSort(r.packages, (p) => p.name);
|
|
1481
|
+
for (const pkg of sortedPackages) {
|
|
1482
|
+
if (!pkg.packageUsage)
|
|
1483
|
+
continue;
|
|
1484
|
+
hasUsageInfo = true;
|
|
1485
|
+
lines.push(`### ${pkg.name}`);
|
|
1486
|
+
lines.push(``);
|
|
1487
|
+
lines.push(`**Total files analyzed:** ${pkg.packageUsage.analyzedFiles}`);
|
|
1488
|
+
lines.push(`**Total packages:** ${pkg.packageUsage.totalPackages}`);
|
|
1489
|
+
lines.push(``);
|
|
1490
|
+
const sortedUsage = Array.from(pkg.packageUsage.usageByPackage.values()).filter((u) => u.fileCount > 0).sort((a, b) => b.fileCount - a.fileCount);
|
|
1491
|
+
if (sortedUsage.length === 0) {
|
|
1492
|
+
lines.push(`No package usage detected in source files.`);
|
|
1493
|
+
lines.push(``);
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
for (const usage of sortedUsage) {
|
|
1497
|
+
const depVersion = pkg.dependencies[usage.packageName] || pkg.devDependencies[usage.packageName] || "";
|
|
1498
|
+
const versionStr = depVersion ? `@${depVersion}` : "";
|
|
1499
|
+
lines.push(`#### ${usage.packageName}${versionStr} (${usage.fileCount} file${usage.fileCount !== 1 ? "s" : ""})`);
|
|
1500
|
+
lines.push(``);
|
|
1501
|
+
if (usage.filePaths.length > 0) {
|
|
1502
|
+
for (const filePath of usage.filePaths) {
|
|
1503
|
+
lines.push(`- ${filePath}`);
|
|
1504
|
+
}
|
|
1505
|
+
} else {
|
|
1506
|
+
lines.push(`- No file paths collected`);
|
|
1507
|
+
}
|
|
1508
|
+
lines.push(``);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (!hasUsageInfo) {
|
|
1513
|
+
lines.push(`No package usage information available. Run with --detailed flag to enable usage analysis.`);
|
|
1514
|
+
lines.push(``);
|
|
1515
|
+
}
|
|
1516
|
+
lines.push(`---`);
|
|
1517
|
+
lines.push(``);
|
|
1518
|
+
lines.push(`## Root Findings`);
|
|
1519
|
+
if (r.findings.length === 0) {
|
|
1520
|
+
lines.push(`No findings for root package.`);
|
|
1521
|
+
} else {
|
|
1522
|
+
const findings = stableSort(r.findings, (f) => `${f.severity}:${f.id}`);
|
|
1523
|
+
for (const f of findings) {
|
|
1524
|
+
lines.push(`### ${f.title} (${badge(f.severity)})`);
|
|
1525
|
+
lines.push(``);
|
|
1526
|
+
for (const d of f.details)
|
|
1527
|
+
lines.push(`- ${d}`);
|
|
1528
|
+
if (f.hints.length > 0) {
|
|
1529
|
+
lines.push(``);
|
|
1530
|
+
lines.push(`**Hints:**`);
|
|
1531
|
+
for (const h of f.hints)
|
|
1532
|
+
lines.push(`- ${h}`);
|
|
1533
|
+
}
|
|
1534
|
+
lines.push(``);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
lines.push(``);
|
|
1538
|
+
return lines.join(`
|
|
1539
|
+
`);
|
|
1540
|
+
};
|
|
1127
1541
|
|
|
1128
1542
|
// src/report_json.ts
|
|
1129
1543
|
function renderJson(r) {
|
|
@@ -1136,7 +1550,7 @@ var usage = () => {
|
|
|
1136
1550
|
"bun-ready",
|
|
1137
1551
|
"",
|
|
1138
1552
|
"Usage:",
|
|
1139
|
-
" bun-ready scan <path> [--format md|json] [--out <file>] [--no-install] [--no-test] [--verbose] [--scope root|packages|all] [--fail-on green|yellow|red]",
|
|
1553
|
+
" bun-ready scan <path> [--format md|json] [--out <file>] [--no-install] [--no-test] [--verbose] [--detailed] [--scope root|packages|all] [--fail-on green|yellow|red]",
|
|
1140
1554
|
"",
|
|
1141
1555
|
"Options:",
|
|
1142
1556
|
" --format md|json Output format (default: md)",
|
|
@@ -1144,6 +1558,7 @@ var usage = () => {
|
|
|
1144
1558
|
" --no-install Skip bun install --dry-run",
|
|
1145
1559
|
" --no-test Skip bun test",
|
|
1146
1560
|
" --verbose Show detailed output",
|
|
1561
|
+
" --detailed Show detailed package usage report with file paths",
|
|
1147
1562
|
" --scope root|packages|all Scan scope for monorepos (default: all)",
|
|
1148
1563
|
" --fail-on green|yellow|red Fail policy (default: red)",
|
|
1149
1564
|
"",
|
|
@@ -1168,6 +1583,7 @@ var parseArgs = (argv) => {
|
|
|
1168
1583
|
runInstall: true,
|
|
1169
1584
|
runTest: true,
|
|
1170
1585
|
verbose: false,
|
|
1586
|
+
detailed: false,
|
|
1171
1587
|
scope: "all"
|
|
1172
1588
|
}
|
|
1173
1589
|
};
|
|
@@ -1178,6 +1594,7 @@ var parseArgs = (argv) => {
|
|
|
1178
1594
|
let runInstall = true;
|
|
1179
1595
|
let runTest = true;
|
|
1180
1596
|
let verbose = false;
|
|
1597
|
+
let detailed = false;
|
|
1181
1598
|
let scope = "all";
|
|
1182
1599
|
let failOn;
|
|
1183
1600
|
for (let i = 2;i < args.length; i++) {
|
|
@@ -1206,6 +1623,10 @@ var parseArgs = (argv) => {
|
|
|
1206
1623
|
verbose = true;
|
|
1207
1624
|
continue;
|
|
1208
1625
|
}
|
|
1626
|
+
if (a === "--detailed") {
|
|
1627
|
+
detailed = true;
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1209
1630
|
if (a === "--scope") {
|
|
1210
1631
|
const v = args[i + 1] ?? "";
|
|
1211
1632
|
if (v === "root" || v === "packages" || v === "all")
|
|
@@ -1228,6 +1649,7 @@ var parseArgs = (argv) => {
|
|
|
1228
1649
|
runInstall,
|
|
1229
1650
|
runTest,
|
|
1230
1651
|
verbose,
|
|
1652
|
+
detailed,
|
|
1231
1653
|
scope
|
|
1232
1654
|
};
|
|
1233
1655
|
if (failOn !== undefined) {
|
|
@@ -1269,7 +1691,14 @@ var main = async () => {
|
|
|
1269
1691
|
`);
|
|
1270
1692
|
process.exit(1);
|
|
1271
1693
|
}
|
|
1272
|
-
const
|
|
1694
|
+
const configOpts = {};
|
|
1695
|
+
if (opts.failOn !== undefined) {
|
|
1696
|
+
configOpts.failOn = opts.failOn;
|
|
1697
|
+
}
|
|
1698
|
+
if (opts.detailed !== undefined) {
|
|
1699
|
+
configOpts.detailed = opts.detailed;
|
|
1700
|
+
}
|
|
1701
|
+
const config = await mergeConfigWithOpts(null, configOpts);
|
|
1273
1702
|
const scanOpts = {
|
|
1274
1703
|
repoPath: opts.repoPath,
|
|
1275
1704
|
format: opts.format,
|
|
@@ -1283,10 +1712,25 @@ var main = async () => {
|
|
|
1283
1712
|
scanOpts.failOn = opts.failOn;
|
|
1284
1713
|
}
|
|
1285
1714
|
const res = await analyzeRepoOverall(scanOpts);
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1715
|
+
if (res.install?.skipReason || res.test?.skipReason) {
|
|
1716
|
+
const skipWarnings = [];
|
|
1717
|
+
if (res.install?.skipReason) {
|
|
1718
|
+
skipWarnings.push(`Install check skipped: ${res.install.skipReason}`);
|
|
1719
|
+
}
|
|
1720
|
+
if (res.test?.skipReason) {
|
|
1721
|
+
skipWarnings.push(`Test run skipped: ${res.test.skipReason}`);
|
|
1722
|
+
}
|
|
1723
|
+
if (skipWarnings.length > 0) {
|
|
1724
|
+
process.stderr.write(`WARNING:
|
|
1725
|
+
${skipWarnings.map((w) => ` - ${w}`).join(`
|
|
1726
|
+
`)}
|
|
1727
|
+
`);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
const out = opts.format === "json" ? renderJson(res) : opts.detailed ? renderDetailedReport(res) : renderMarkdown(res);
|
|
1731
|
+
const target = opts.outFile ?? (opts.format === "json" ? "bun-ready.json" : opts.detailed ? "bun-ready-detailed.md" : "bun-ready.md");
|
|
1732
|
+
const resolved = path6.resolve(process.cwd(), target);
|
|
1733
|
+
await fs4.writeFile(resolved, out, "utf8");
|
|
1290
1734
|
process.stdout.write(`Wrote ${opts.format.toUpperCase()} report to ${resolved}
|
|
1291
1735
|
`);
|
|
1292
1736
|
process.exit(exitCode(res.severity, config?.failOn || opts.failOn));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bun-ready",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "CLI that estimates how painful migrating a Node.js repo to Bun might be. Generates a green/yellow/red Markdown report with reasons.",
|
|
5
5
|
"author": "Pas7 Studio",
|
|
6
6
|
"license": "Apache-2.0",
|