@mh-alikhani/bunready 0.1.0 → 0.2.0
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 +28 -0
- package/README.md +23 -6
- package/docs/brand/guidelines.md +2 -0
- package/docs/brand/logo-auto.svg +28 -0
- package/package.json +17 -4
- package/src/rules/data/node-runtime.json +112 -3
- package/src/rules/install/lifecycle-scripts.ts +22 -4
- package/src/rules/runtime/builtins.ts +3 -1
- package/src/scanner/sources.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
Nothing yet.
|
|
11
11
|
|
|
12
|
+
## [0.2.0] - 2026-09-16
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Sourced runtime gap dataset.** `src/rules/data/node-runtime.json` now carries
|
|
17
|
+
18 entries read from Bun's own Node.js compatibility table (17 `partial`,
|
|
18
|
+
1 `unimplemented`), each with the table as its primary source. A repository
|
|
19
|
+
importing a `partial` module gets a `risk`; an `unimplemented` one
|
|
20
|
+
(currently `node:sea`) is a `blocker`, because it breaks on import with no
|
|
21
|
+
user action.
|
|
22
|
+
- **Real-project smoke script.** `bun run smoke` downloads the Next.js
|
|
23
|
+
hello-world example, the NestJS starter and turborepo's basic monorepo and
|
|
24
|
+
prints each scan's verdict, so a release is validated against real
|
|
25
|
+
repositories before it ships.
|
|
26
|
+
- Decisions D41–D43 in `STATE.md` (optional-dependency severity, `node:`
|
|
27
|
+
prefix classification, the smoke script).
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **Optional dependencies no longer produce blockers.** A skipped install
|
|
32
|
+
script on an *optional* package (the `fsevents` false positive on the NestJS
|
|
33
|
+
starter) is now a `risk`: the installer tolerates an absent optional package
|
|
34
|
+
by design, so a skipped script cannot break the install itself.
|
|
35
|
+
- **`node:`-prefixed imports are always classified as Node built-ins**, even
|
|
36
|
+
when the runtime's own `builtinModules` list lags behind Node (e.g.
|
|
37
|
+
`node:sea` is missing from Bun's list).
|
|
38
|
+
- README no longer claims `--run` is unimplemented.
|
|
39
|
+
|
|
12
40
|
## [0.1.0] - 2026-09-09
|
|
13
41
|
|
|
14
42
|
### Added
|
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/brand/logo-dark.svg">
|
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/brand/logo.svg">
|
|
6
|
+
<img src="docs/brand/logo-auto.svg" alt="bunready" width="360">
|
|
7
|
+
</picture>
|
|
4
8
|
|
|
5
9
|
**One command that tells you what will break before you move a Node/TS repo to Bun — and gives you one clear verdict.**
|
|
6
10
|
|
|
@@ -24,9 +28,10 @@ Pre-alpha, and precise about it. `bunready <path>` reads `package.json`, any
|
|
|
24
28
|
lockfile, and the repository's own imports, then prints a report with a verdict
|
|
25
29
|
and an exit code CI can gate on.
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
`--run` goes further: it copies the target to a temporary directory, installs
|
|
32
|
+
and boots it under Bun there, and reports the first real failure with timings.
|
|
33
|
+
Every compatibility claim carries a source link, so you can check the
|
|
34
|
+
underlying evidence yourself. See [STATE.md](STATE.md) for details.
|
|
30
35
|
|
|
31
36
|
## Install
|
|
32
37
|
|
|
@@ -57,7 +62,7 @@ bunx @mh-alikhani/bunready /path/to/node-project
|
|
|
57
62
|
| `--help` | Print usage and exit. |
|
|
58
63
|
| `--version` | Print the CLI version and exit. |
|
|
59
64
|
| `--json` | Emit machine-readable JSON instead of the terminal report. |
|
|
60
|
-
| `--run` |
|
|
65
|
+
| `--run [script]` | Copy the target to a temporary directory, `bun install` and run a script (default `start`) there under Bun, and report the first failure with timings. Never executes in place; a timeout is a `risk`, a pass is `info`. |
|
|
61
66
|
| `NO_COLOR` | Environment variable: disable ANSI color when set. |
|
|
62
67
|
|
|
63
68
|
Exit codes: `0` no blockers, `1` blockers found, `2` usage error.
|
|
@@ -88,7 +93,7 @@ permissions:
|
|
|
88
93
|
|
|
89
94
|
steps:
|
|
90
95
|
- uses: actions/checkout@v7
|
|
91
|
-
- uses: MHAlikhani/bunready@v0.
|
|
96
|
+
- uses: MHAlikhani/bunready@v0.2.0
|
|
92
97
|
with:
|
|
93
98
|
path: .
|
|
94
99
|
```
|
|
@@ -97,6 +102,18 @@ The action writes a JSON report, uploads the SARIF report to code scanning and
|
|
|
97
102
|
fails the step when findings at or above `failOn` exist. Inputs: `path`,
|
|
98
103
|
`version` (`latest` or `local`), `sarif-file`, `json-file`, `upload`.
|
|
99
104
|
|
|
105
|
+
## How it is different
|
|
106
|
+
|
|
107
|
+
- **Evidence, not estimates.** Every finding records what was observed in your
|
|
108
|
+
repository, and every compatibility claim links to Bun's documentation or an
|
|
109
|
+
issue. Nothing is inferred from a package name.
|
|
110
|
+
- **Built for CI.** Stable exit codes, `--json` with a versioned schema,
|
|
111
|
+
`--sarif` for code scanning, and baselines so a repository can fail on what is
|
|
112
|
+
new instead of on its whole history.
|
|
113
|
+
- **Whole-repository aware.** Workspace packages are scanned and aggregated, and
|
|
114
|
+
`--run` installs and boots the project in a temporary copy to catch the first
|
|
115
|
+
real failure rather than predicting one.
|
|
116
|
+
|
|
100
117
|
## Why
|
|
101
118
|
|
|
102
119
|
Moving a repo to Bun is usually a pile of small unknowns: which npm lifecycle
|
package/docs/brand/guidelines.md
CHANGED
|
@@ -28,6 +28,7 @@ own: every severity also prints as a word.
|
|
|
28
28
|
| --- | --- |
|
|
29
29
|
| `docs/brand/logo.svg` | Default lockup, light backgrounds. |
|
|
30
30
|
| `docs/brand/logo-dark.svg` | Lockup on dark backgrounds. |
|
|
31
|
+
| `docs/brand/logo-auto.svg` | Theme-aware lockup: follows `prefers-color-scheme` itself. Used where `<picture>` is stripped, and as the README fallback. |
|
|
31
32
|
| `docs/brand/logo-mono.svg` | Single-colour lockup; inherits `currentColor`. |
|
|
32
33
|
| `docs/brand/mark.svg` | Mark alone; inherits `currentColor`. |
|
|
33
34
|
| `docs/brand/favicon.svg` | Favicon: mark on ink, tuned for 16px. |
|
|
@@ -38,6 +39,7 @@ square: "you are about to run something".
|
|
|
38
39
|
|
|
39
40
|
**Clear space:** 0.5 x the mark height on all four sides. Nothing enters it.
|
|
40
41
|
**Minimum sizes:** mark 24px; lockup 120px wide.
|
|
42
|
+
**Colour scheme:** every lockup must stay legible on both `#FBFAF7` and `#131519`. Where the host supports it, serve `logo.svg` / `logo-dark.svg` through `<picture>` with `prefers-color-scheme`; otherwise serve `logo-auto.svg`, which switches itself. Never ship the light lockup into a dark-only context.
|
|
41
43
|
**Never:** recolour outside `tokens.json`, stretch, rotate, add effects,
|
|
42
44
|
place the lockup on a busy photo, or crop the mark's rounded square.
|
|
43
45
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 340 120" width="340" height="120" role="img" aria-label="bunready">
|
|
2
|
+
<title>bunready</title>
|
|
3
|
+
<!--
|
|
4
|
+
One lockup that follows the reader's colour scheme. The light palette is
|
|
5
|
+
baked into the shapes so that a sanitizer which strips <style> (some
|
|
6
|
+
registries do) still renders a legible mark; the media query only overrides
|
|
7
|
+
for dark. Prefer .svg light/dark pairs with <picture> when the host supports
|
|
8
|
+
it, and this file when it does not.
|
|
9
|
+
-->
|
|
10
|
+
<style>
|
|
11
|
+
.ring { stroke: #a85b2b; }
|
|
12
|
+
.glyph { stroke: #171a1f; }
|
|
13
|
+
.wordmark { fill: #171a1f; }
|
|
14
|
+
@media (prefers-color-scheme: dark) {
|
|
15
|
+
.ring { stroke: #e0a268; }
|
|
16
|
+
.glyph { stroke: #f2f1ec; }
|
|
17
|
+
.wordmark { fill: #f2f1ec; }
|
|
18
|
+
}
|
|
19
|
+
</style>
|
|
20
|
+
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
21
|
+
<rect class="ring" x="34" y="34" width="52" height="52" rx="15" stroke-width="3"/>
|
|
22
|
+
<path class="glyph" d="M53 49 L68 60 L53 71" stroke-width="4.5"/>
|
|
23
|
+
<path class="glyph" d="M54 77 H68" stroke-width="4.5"/>
|
|
24
|
+
</g>
|
|
25
|
+
<text class="wordmark" x="108" y="76"
|
|
26
|
+
font-family="ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
|
|
27
|
+
font-size="42" font-weight="620" letter-spacing="-1">bunready</text>
|
|
28
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mh-alikhani/bunready",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
7
|
+
"bun-readiness",
|
|
8
|
+
"node",
|
|
9
|
+
"nodejs",
|
|
10
|
+
"typescript",
|
|
7
11
|
"migration",
|
|
8
12
|
"compatibility",
|
|
9
|
-
"scanner",
|
|
10
13
|
"cli",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
14
|
+
"scanner",
|
|
15
|
+
"static-analysis",
|
|
16
|
+
"monorepo",
|
|
17
|
+
"workspaces",
|
|
18
|
+
"lockfile",
|
|
19
|
+
"sarif",
|
|
20
|
+
"ci",
|
|
21
|
+
"code-quality",
|
|
22
|
+
"developer-tools",
|
|
23
|
+
"devtools",
|
|
24
|
+
"dependency-analysis"
|
|
13
25
|
],
|
|
14
26
|
"license": "MIT",
|
|
15
27
|
"author": "Mohammad Hosein Alikhani",
|
|
@@ -45,6 +57,7 @@
|
|
|
45
57
|
"build": "bun build ./src/cli/index.ts --target=bun --outdir=dist",
|
|
46
58
|
"check": "bun run typecheck && bun run lint && bun run test",
|
|
47
59
|
"og": "bun run scripts/generate-og-image.ts",
|
|
60
|
+
"smoke": "bun run scripts/smoke-real.ts",
|
|
48
61
|
"prepare": "simple-git-hooks"
|
|
49
62
|
},
|
|
50
63
|
"devDependencies": {
|
|
@@ -1,6 +1,115 @@
|
|
|
1
1
|
{
|
|
2
|
-
"datasetVersion": "2026-09-
|
|
2
|
+
"datasetVersion": "2026-09-16.1",
|
|
3
3
|
"compatibilityDocs": "https://bun.com/docs/runtime/nodejs-compat",
|
|
4
|
-
"note": "Runtime compatibility data.
|
|
5
|
-
"gaps": [
|
|
4
|
+
"note": "Runtime compatibility data. Every entry below was read from Bun's own Node.js compatibility table (the `source` URL); see docs/adr/0001-data-source-policy.md. `status: partial` means part of the module's surface is missing; `status: unimplemented` means importing/using it fails outright.",
|
|
5
|
+
"gaps": [
|
|
6
|
+
{
|
|
7
|
+
"name": "sea",
|
|
8
|
+
"status": "unimplemented",
|
|
9
|
+
"note": "Single-executable applications are not implemented in Bun; Bun documents `bun build --compile` as the replacement.",
|
|
10
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "async_hooks",
|
|
14
|
+
"status": "partial",
|
|
15
|
+
"note": "AsyncLocalStorage and AsyncResource work; createHook and related are stubs with async ids always 0, and context does not propagate into MessagePort/BroadcastChannel/Worker events.",
|
|
16
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "child_process",
|
|
20
|
+
"status": "partial",
|
|
21
|
+
"note": "IPC handles net/dgram sockets but not http server sockets; 'advanced' serialization only works between Bun processes; channel.ref()/unref() are missing.",
|
|
22
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "cluster",
|
|
26
|
+
"status": "partial",
|
|
27
|
+
"note": "net/dgram sharing works as in Node; HTTP load-balancing across processes works only on Linux via SO_REUSEPORT and is not battle-tested.",
|
|
28
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "crypto",
|
|
32
|
+
"status": "partial",
|
|
33
|
+
"note": "BoringSSL-backed: ed448, x448, rsa-pss, dsa, dh, ML-KEM-512, secp256k1 and several ciphers are unavailable; encapsulate/decapsulate are missing; setEngine() throws and setFips() is a no-op.",
|
|
34
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "diagnostics_channel",
|
|
38
|
+
"status": "partial",
|
|
39
|
+
"note": "channel(), subscribe(), tracingChannel() and the http/http2/dgram channels work; boundedChannel() and several built-in channels are missing, and subscribers do not keep a Channel alive.",
|
|
40
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "domain",
|
|
44
|
+
"status": "partial",
|
|
45
|
+
"note": "Only synchronous errors are caught in run()/bind() or by emitters added with add(); asynchronous errors are not routed to the domain.",
|
|
46
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "https",
|
|
50
|
+
"status": "partial",
|
|
51
|
+
"note": "request, get, Agent and globalAgent are implemented with pooling; https.Server is http.Server with TLS options and lacks getPeerCertificate(), getCipher(), setSecureContext(), SNICallback and handshakeTimeout.",
|
|
52
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "inspector",
|
|
56
|
+
"status": "partial",
|
|
57
|
+
"note": "Session supports Profiler, Runtime.enable and NodeTracing; Debugger commands are forwarded but results are not returned; Runtime.evaluate and HeapProfiler are unimplemented and there is no Network domain.",
|
|
58
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "module",
|
|
62
|
+
"status": "partial",
|
|
63
|
+
"note": "Module#load(), registerHooks, findPackageJSON, stripTypeScriptTypes and source-map support APIs are missing; some methods are no-ops.",
|
|
64
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "perf_hooks",
|
|
68
|
+
"status": "partial",
|
|
69
|
+
"note": "monitorEventLoopDelay, createHistogram, timerify and PerformanceObserver are implemented; gc/dns/resource entries are never emitted and eventLoopUtilization() returns zeros.",
|
|
70
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "process",
|
|
74
|
+
"status": "partial",
|
|
75
|
+
"note": "process.binding is only partially available; setting process.title is a no-op on macOS/Linux; several methods return empty arrays; sourceMapsEnabled and addUncaughtExceptionCaptureCallback are missing.",
|
|
76
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "repl",
|
|
80
|
+
"status": "partial",
|
|
81
|
+
"note": "Mostly implemented via bun --interactive; there are no result previews and tab-completion skips let/const/class bindings.",
|
|
82
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "test",
|
|
86
|
+
"status": "partial",
|
|
87
|
+
"note": "The in-process API works under bun test; run() is limited and reporters, snapshots, coverage, --test-only and the CLI runner mode are missing.",
|
|
88
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "tls",
|
|
92
|
+
"status": "partial",
|
|
93
|
+
"note": "pskCallback, OCSP stapling and cross-process session resumption are missing; renegotiate() always fails because the TLS layer is BoringSSL.",
|
|
94
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "v8",
|
|
98
|
+
"status": "partial",
|
|
99
|
+
"note": "Heap snapshots/statistics and GCProfiler are implemented; serialize/deserialize use JSC's wire format; queryObjects, CPU/heap profiles, Serializer/Deserializer and promiseHooks are missing.",
|
|
100
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "wasi",
|
|
104
|
+
"status": "partial",
|
|
105
|
+
"note": "args, env, preopens, wasiImport and start() work; getImportObject(), initialize() and sock_accept are missing and several options are ignored.",
|
|
106
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "worker_threads",
|
|
110
|
+
"status": "partial",
|
|
111
|
+
"note": "resourceLimits and trackUnmanagedFds are ignored; some execArgv flags are only partly applied; moveMessagePortToContext and locks are missing.",
|
|
112
|
+
"source": "https://bun.com/docs/runtime/nodejs-compat"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
6
115
|
}
|
|
@@ -25,11 +25,16 @@ export function lifecycleScriptFindings(
|
|
|
25
25
|
lockfile: ParsedLockfile | undefined,
|
|
26
26
|
): Finding[] {
|
|
27
27
|
const trusted = new Set(snapshot.manifest.trustedDependencies);
|
|
28
|
-
const evidence = new Map<string, string>();
|
|
28
|
+
const evidence = new Map<string, { where: string; optional: boolean }>();
|
|
29
29
|
|
|
30
30
|
for (const pkg of lockfile?.packages ?? []) {
|
|
31
31
|
if (pkg.installScript) {
|
|
32
|
-
evidence.set(pkg.name,
|
|
32
|
+
evidence.set(pkg.name, {
|
|
33
|
+
where: `the lockfile marks ${pkg.name} as requiring a build step`,
|
|
34
|
+
// An optional dependency is tolerated absent by design, so a skipped
|
|
35
|
+
// install script can never break the install itself (e.g. fsevents).
|
|
36
|
+
optional: pkg.optional,
|
|
37
|
+
});
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
@@ -38,12 +43,12 @@ export function lifecycleScriptFindings(
|
|
|
38
43
|
continue;
|
|
39
44
|
}
|
|
40
45
|
const scripts = probe.installScripts.map((name) => `"${name}"`).join(", ");
|
|
41
|
-
evidence.set(probe.name, `${probe.path} declares ${scripts}
|
|
46
|
+
evidence.set(probe.name, { where: `${probe.path} declares ${scripts}`, optional: false });
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
return [...evidence.entries()]
|
|
45
50
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
46
|
-
.map(([name, where]) => {
|
|
51
|
+
.map(([name, { where, optional }]) => {
|
|
47
52
|
if (trusted.has(name)) {
|
|
48
53
|
return {
|
|
49
54
|
id: ID,
|
|
@@ -55,6 +60,19 @@ export function lifecycleScriptFindings(
|
|
|
55
60
|
evidence: where,
|
|
56
61
|
};
|
|
57
62
|
}
|
|
63
|
+
if (optional) {
|
|
64
|
+
return {
|
|
65
|
+
id: ID,
|
|
66
|
+
severity: "risk" as const,
|
|
67
|
+
title: `${name} is an optional dependency whose install script will not run`,
|
|
68
|
+
package: name,
|
|
69
|
+
detail:
|
|
70
|
+
"Bun installs dependencies without running their lifecycle scripts unless the package is listed in trustedDependencies. Because this package is optional, the install still succeeds without it - but if it does install on this platform and needs its build step, it will be broken.",
|
|
71
|
+
evidence: where,
|
|
72
|
+
hint: `check whether ${name} is actually used on this platform; if it is, add it to trustedDependencies in package.json and reinstall.`,
|
|
73
|
+
source: LIFECYCLE_DOC,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
58
76
|
return {
|
|
59
77
|
id: ID,
|
|
60
78
|
severity: "blocker" as const,
|
|
@@ -121,9 +121,11 @@ export function runtimeBuiltinFindings(
|
|
|
121
121
|
if (usage === undefined) {
|
|
122
122
|
continue;
|
|
123
123
|
}
|
|
124
|
+
// A `partial` module usually still runs; an `unimplemented` module breaks
|
|
125
|
+
// on import with no user action, which is the blocker bar (D2).
|
|
124
126
|
findings.push({
|
|
125
127
|
id: GAP_ID,
|
|
126
|
-
severity: "risk",
|
|
128
|
+
severity: gap.status === "unimplemented" ? "blocker" : "risk",
|
|
127
129
|
title: `${gap.name} is ${gap.status} in Bun and this project imports it`,
|
|
128
130
|
detail: gap.note,
|
|
129
131
|
evidence: `${usage.files.length} file(s), first at ${usage.files[0] ?? "unknown"}`,
|
package/src/scanner/sources.ts
CHANGED
|
@@ -270,7 +270,9 @@ export function classifySpecifier(
|
|
|
270
270
|
if (specifier.startsWith("bun:")) {
|
|
271
271
|
return "bun-builtin";
|
|
272
272
|
}
|
|
273
|
-
|
|
273
|
+
// A `node:` prefix is unambiguous even when the runtime's own module list
|
|
274
|
+
// lags behind Node (e.g. node:sea is absent from Bun's builtinModules).
|
|
275
|
+
if (specifier.startsWith("node:") || builtins.has(specifier)) {
|
|
274
276
|
return "node-builtin";
|
|
275
277
|
}
|
|
276
278
|
if (
|