@mh-alikhani/bunready 0.3.2 → 0.3.4
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 +47 -4
- package/README.md +70 -91
- package/docs/README.md +16 -0
- package/docs/adr/0001-data-source-policy.md +1 -1
- package/docs/adr/0002-rule-severity-model.md +1 -1
- package/docs/adr/0003-release-pipeline.md +1 -1
- package/package.json +1 -1
- package/src/cli/args.ts +1 -0
- package/src/cli/copy.ts +4 -0
- package/src/cli/io.ts +1 -0
- package/src/cli/run.ts +4 -0
- package/src/cli/theme.ts +2 -0
- package/src/config/baseline.ts +5 -0
- package/src/config/config.ts +5 -0
- package/src/core/errors.ts +6 -0
- package/src/core/fs.ts +2 -0
- package/src/core/version.ts +2 -1
- package/src/report/human.ts +1 -0
- package/src/report/sarif.ts +1 -0
- package/src/report/types.ts +14 -2
- package/src/rules/install/lifecycle-scripts.ts +2 -0
- package/src/rules/install/lockfile-presence.ts +1 -0
- package/src/rules/install/native-addon.ts +3 -1
- package/src/rules/run/index.ts +1 -0
- package/src/rules/runtime/builtins.ts +5 -1
- package/src/rules/severity.ts +3 -0
- package/src/scanner/execute.ts +14 -0
- package/src/scanner/graph.ts +1 -0
- package/src/scanner/lockfile.ts +5 -0
- package/src/scanner/manifest.ts +1 -0
- package/src/scanner/scan.ts +35 -11
- package/src/scanner/semver.ts +4 -0
- package/src/scanner/sources.ts +117 -34
- package/src/scanner/target.ts +4 -0
- package/src/scanner/workspaces.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -9,7 +9,48 @@ 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.
|
|
12
|
+
## [0.3.4] - 2026-09-16
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Every exported symbol carries a doc comment. JSR scores how much of a
|
|
17
|
+
package's exported surface is documented; this package was at 48% and
|
|
18
|
+
is now at 100%. Comments only - no behaviour changed.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## [0.3.3] - 2026-09-16
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- `targets[].path` and `report.target` are normalised like `finding.path`, so
|
|
26
|
+
grouping findings by target works on Windows as well as POSIX. The new
|
|
27
|
+
per-target tests caught this.
|
|
28
|
+
- Per-target verdicts and counts are computed from the findings that survived
|
|
29
|
+
configuration, so a package can no longer read "blocked" while the report says
|
|
30
|
+
"ready" because its blocker was ignored.
|
|
31
|
+
- An excluded path no longer consumes the source-file budget: the scan used to
|
|
32
|
+
report itself as truncated for files it was never going to read.
|
|
33
|
+
- Template-literal interpolations are scanned as code. `${require("node:fs")}`
|
|
34
|
+
is an import; template text that merely looks like one is still ignored.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- A workspace root no longer re-walks its packages' sources. Benchmark, 8
|
|
39
|
+
packages of 800 files each: 410ms and 8,400 files read became 303ms and 6,400 -
|
|
40
|
+
now exactly one package's worth per target.
|
|
41
|
+
- Line numbers come from one newline map per file instead of a rescan per match.
|
|
42
|
+
- `sortFindings` is documented and tested as a total order (severity, id, title,
|
|
43
|
+
path), so output is identical however the findings were collected.
|
|
44
|
+
- The README is rebuilt around a quickstart, what it checks, CI, FAQ and a
|
|
45
|
+
documentation index.
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- `bun run bench` measures a single package and a workspace.
|
|
50
|
+
- `docs/README.md` indexes the documentation.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## [0.3.2] - 2026-09-16
|
|
13
54
|
|
|
14
55
|
### Changed
|
|
15
56
|
|
|
@@ -24,7 +65,7 @@ Nothing yet.
|
|
|
24
65
|
size is the Bun runtime), and deferring the scanner behind dynamic imports moved
|
|
25
66
|
the cost into the scan path without a repeatable win.
|
|
26
67
|
|
|
27
|
-
## [0.3.1] - 2026-09-
|
|
68
|
+
## [0.3.1] - 2026-09-16
|
|
28
69
|
|
|
29
70
|
### Fixed
|
|
30
71
|
|
|
@@ -37,7 +78,7 @@ Nothing yet.
|
|
|
37
78
|
Behaviour is unchanged; `tests/workspaces.test.ts` and `tests/semver.test.ts`
|
|
38
79
|
cover the affected paths.
|
|
39
80
|
|
|
40
|
-
## [0.3.0] - 2026-09-
|
|
81
|
+
## [0.3.0] - 2026-09-16
|
|
41
82
|
|
|
42
83
|
### Added
|
|
43
84
|
|
|
@@ -216,7 +257,9 @@ Nothing yet.
|
|
|
216
257
|
and that claim needs a primary source. Until then the rule reports what the
|
|
217
258
|
repository imports and cites the compatibility table.
|
|
218
259
|
|
|
219
|
-
[Unreleased]: https://github.com/MHAlikhani/bunready/compare/v0.3.
|
|
260
|
+
[Unreleased]: https://github.com/MHAlikhani/bunready/compare/v0.3.4...HEAD
|
|
261
|
+
[0.3.4]: https://github.com/MHAlikhani/bunready/releases/tag/v0.3.4
|
|
262
|
+
[0.3.3]: https://github.com/MHAlikhani/bunready/releases/tag/v0.3.3
|
|
220
263
|
[0.3.2]: https://github.com/MHAlikhani/bunready/releases/tag/v0.3.2
|
|
221
264
|
[0.3.1]: https://github.com/MHAlikhani/bunready/releases/tag/v0.3.1
|
|
222
265
|
[0.3.0]: https://github.com/MHAlikhani/bunready/releases/tag/v0.3.0
|
package/README.md
CHANGED
|
@@ -6,141 +6,120 @@
|
|
|
6
6
|
<img src="docs/brand/logo-card.svg" alt="bunready" width="360">
|
|
7
7
|
</picture>
|
|
8
8
|
|
|
9
|
-
**
|
|
9
|
+
**Know what breaks before you move a Node/TS repo to Bun.**
|
|
10
|
+
|
|
11
|
+
One command. One honest verdict. Evidence, not estimates — and it runs in CI.
|
|
10
12
|
|
|
11
13
|
[](https://github.com/MHAlikhani/bunready/actions/workflows/ci.yml)
|
|
12
14
|
[](https://github.com/MHAlikhani/bunready/actions/workflows/security.yml)
|
|
15
|
+
[](https://www.npmjs.com/package/@mh-alikhani/bunready)
|
|
13
16
|
[](LICENSE)
|
|
14
17
|
[](https://bun.sh)
|
|
15
18
|
[](tsconfig.json)
|
|
16
|
-
[](#status)
|
|
17
|
-
|
|
18
|
-
[](https://www.npmjs.com/package/@mh-alikhani/bunready)
|
|
19
|
-
-->
|
|
20
19
|
|
|
21
20
|
</div>
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## Status
|
|
26
|
-
|
|
27
|
-
Pre-alpha, and precise about it. `bunready <path>` reads `package.json`, any
|
|
28
|
-
lockfile, and the repository's own imports, then prints a report with a verdict
|
|
29
|
-
and an exit code CI can gate on.
|
|
30
|
-
|
|
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.
|
|
35
|
-
|
|
36
|
-
## Install
|
|
22
|
+
## Quickstart
|
|
37
23
|
|
|
38
24
|
```sh
|
|
39
|
-
bunx @mh-alikhani/bunready . #
|
|
40
|
-
|
|
25
|
+
bunx @mh-alikhani/bunready . # scan the current directory
|
|
26
|
+
bunx @mh-alikhani/bunready . --json # machine-readable report
|
|
27
|
+
bunx @mh-alikhani/bunready --help # every flag
|
|
41
28
|
```
|
|
42
29
|
|
|
43
|
-
|
|
30
|
+
Scanning this repository prints its findings and one verdict:
|
|
44
31
|
|
|
45
|
-
```sh
|
|
46
|
-
git clone https://github.com/MHAlikhani/bunready.git
|
|
47
|
-
cd bunready
|
|
48
|
-
bun install
|
|
49
32
|
```
|
|
33
|
+
bunready 0.3.3 · 106 locked packages · bun.lock
|
|
34
|
+
/path/to/your/project
|
|
50
35
|
|
|
51
|
-
|
|
36
|
+
info the project's own code imports 4 Node built-in module(s) (runtime/node-builtins)
|
|
37
|
+
Bun implements a large and still-moving part of the Node API…
|
|
38
|
+
evidence: fs/promises, module, os, path (in 6 file(s))
|
|
39
|
+
source: https://bun.com/docs/runtime/nodejs-compat
|
|
52
40
|
|
|
53
|
-
|
|
54
|
-
bunx @mh-alikhani/bunready --help
|
|
55
|
-
|
|
56
|
-
# against a target repository
|
|
57
|
-
bunx @mh-alikhani/bunready /path/to/node-project
|
|
41
|
+
ready - no Bun compatibility blockers found
|
|
58
42
|
```
|
|
59
43
|
|
|
60
|
-
|
|
61
|
-
| --- | --- |
|
|
62
|
-
| `--help` | Print usage and exit. |
|
|
63
|
-
| `--version` | Print the CLI version and exit. |
|
|
64
|
-
| `--json` | Emit machine-readable JSON instead of the terminal report. |
|
|
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`. |
|
|
66
|
-
| `NO_COLOR` | Environment variable: disable ANSI color when set. |
|
|
44
|
+
Exit codes are the contract: **0** nothing at or above your threshold, **1** findings, **2** usage error.
|
|
67
45
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
## Monorepos
|
|
46
|
+
## What it checks
|
|
71
47
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
48
|
+
| Phase | Rules | Why it matters |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| Install | Blocked lifecycle scripts, native addons, `engines` conflicts, lockfile gaps (`bun.lockb`, missing or unreadable lockfiles) | `bun install` behaves differently from npm: untrusted packages' install scripts do not run, and native builds need a toolchain |
|
|
51
|
+
| Runtime | The Node built-ins your own code imports, with a link to Bun's compatibility table | Tells you which surface to test rather than guessing |
|
|
52
|
+
| Run (`--run`) | Installs and boots your `start`/`test` script in a temporary copy, then reports the first real failure with its stack frames | The only way to know your project actually runs |
|
|
76
53
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```sh
|
|
80
|
-
bunready . --write-baseline bunready.baseline.json # accept today's findings
|
|
81
|
-
bunready . --baseline bunready.baseline.json # fail only on new ones
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
A baseline records rule, package and path - not the message - so rewording a
|
|
85
|
-
finding does not resurrect it.
|
|
54
|
+
Every finding carries the evidence it rests on and, when it makes a compatibility claim, a link to the Bun documentation or issue that supports it — see [ADR 0001](docs/adr/0001-data-source-policy.md). bunready never invents compatibility facts.
|
|
86
55
|
|
|
87
56
|
## In CI
|
|
88
57
|
|
|
89
58
|
```yaml
|
|
90
59
|
permissions:
|
|
91
60
|
contents: read
|
|
92
|
-
security-events: write #
|
|
61
|
+
security-events: write # for the SARIF upload
|
|
93
62
|
|
|
94
63
|
steps:
|
|
95
64
|
- uses: actions/checkout@v7
|
|
96
|
-
- uses: MHAlikhani/bunready@v0.
|
|
65
|
+
- uses: MHAlikhani/bunready@v0.3.3
|
|
97
66
|
with:
|
|
98
67
|
path: .
|
|
99
68
|
```
|
|
100
69
|
|
|
101
|
-
The action writes a JSON report, uploads
|
|
102
|
-
|
|
103
|
-
|
|
70
|
+
The [action](action.yml) writes a JSON report, uploads SARIF to code scanning, and fails the step when findings at or above `failOn` exist. It is also [on the GitHub Marketplace](https://github.com/marketplace/actions/bunready).
|
|
71
|
+
|
|
72
|
+
## Monorepos
|
|
73
|
+
|
|
74
|
+
A `workspaces` field or a `pnpm-workspace.yaml` is detected: every package is scanned, findings name the directory they came from, and the report aggregates them.
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
bunready . --scope packages/api # one package
|
|
78
|
+
bunready . --sarif > bunready.sarif # SARIF 2.1.0 for code scanning
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Baselines
|
|
82
|
+
|
|
83
|
+
Accept today's findings, then fail only on what is new:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
bunready . --write-baseline bunready.baseline.json
|
|
87
|
+
bunready . --baseline bunready.baseline.json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A fingerprint is rule + package + path — not the message — so rewording a finding does not resurrect one you already triaged.
|
|
104
91
|
|
|
105
|
-
##
|
|
92
|
+
## Configuration
|
|
106
93
|
|
|
107
|
-
|
|
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.
|
|
94
|
+
`bunready.config.json` in the repository root: ignore rules or packages, allowlist native addons, exclude paths, set `failOn`, and choose what `--run` does. See [docs/CONFIGURATION.md](docs/CONFIGURATION.md).
|
|
116
95
|
|
|
117
|
-
##
|
|
96
|
+
## Documentation
|
|
118
97
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
98
|
+
| Document | What it covers |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| [Configuration](docs/CONFIGURATION.md) | Every key in `bunready.config.json` |
|
|
101
|
+
| [JSON output](docs/JSON-OUTPUT.md) | The `--json` contract, `schemaVersion`, exit codes |
|
|
102
|
+
| [Releasing](docs/RELEASING.md) | How a release is cut and verified |
|
|
103
|
+
| [ADRs](docs/adr/) | Data source policy, severity model, release pipeline |
|
|
104
|
+
| [Brand](docs/brand/guidelines.md) | Logo, tokens, voice |
|
|
105
|
+
| [State](STATE.md) | Decisions and open questions, kept current |
|
|
106
|
+
|
|
107
|
+
## FAQ
|
|
123
108
|
|
|
124
|
-
|
|
109
|
+
**Does it need network access?** No. A scan reads your repository and nothing else. Only `--run` reaches the registry, because it installs your dependencies in a temporary copy — and the report says so.
|
|
125
110
|
|
|
126
|
-
|
|
127
|
-
entries, shipped as versioned JSON with source links. We never invent
|
|
128
|
-
compatibility facts. See [ADR 0001](docs/adr/0001-data-source-policy.md).
|
|
111
|
+
**Does it modify my repository?** No. Scans are read-only. `--run` copies the project to a temporary directory, excluding `.git`, `node_modules` and build output, and removes it afterwards.
|
|
129
112
|
|
|
130
|
-
|
|
113
|
+
**Why is the npm name scoped?** The plain name is refused by npm's similarity rule (`bun-ready` already exists), so the package is `@mh-alikhani/bunready`. The command, repository and action are all still `bunready`.
|
|
131
114
|
|
|
132
|
-
|
|
133
|
-
[Code of Conduct](CODE_OF_CONDUCT.md). Security reports go through
|
|
134
|
-
[SECURITY.md](SECURITY.md).
|
|
115
|
+
**How is it different from an estimate-style checker?** Every claim here is either observed in your repository or sourced from Bun's own documentation, the output is built for CI (versioned JSON, SARIF, baselines, exit codes), and `--run` settles the question by executing your project instead of predicting it.
|
|
135
116
|
|
|
136
|
-
|
|
117
|
+
**Does it support monorepos?** Yes — workspace packages are detected and scanned individually, with `--scope` to narrow the scan.
|
|
118
|
+
|
|
119
|
+
## Status
|
|
137
120
|
|
|
138
|
-
|
|
139
|
-
endorsed by, or sponsored by the Bun project, Oven**. "Bun" and
|
|
140
|
-
the Bun logo are trademarks of their respective owners and are used here only
|
|
141
|
-
for descriptive, nominative purposes. bunready ships no Bun code and no Bun
|
|
142
|
-
branding.
|
|
121
|
+
Pre-alpha, and precise about it. Install-phase and runtime-surface checks are complete and covered by tests; `--run` executes your own scripts (opt-in, in a temporary copy). What is not done is listed in [STATE.md](STATE.md#open-questions), and the 0.3.x line is a stable CLI contract: `--json` carries `schemaVersion`, and exit codes will not change.
|
|
143
122
|
|
|
144
|
-
##
|
|
123
|
+
## Licence
|
|
145
124
|
|
|
146
|
-
[MIT](LICENSE)
|
|
125
|
+
[MIT](LICENSE). Not affiliated with the Bun project or Oven.
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
| Document | What it covers |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| [CONFIGURATION.md](CONFIGURATION.md) | Every key in `bunready.config.json`, and what configuration deliberately cannot do |
|
|
6
|
+
| [JSON-OUTPUT.md](JSON-OUTPUT.md) | The `--json` contract: `schemaVersion`, field meanings, exit codes |
|
|
7
|
+
| [RELEASING.md](RELEASING.md) | How a release is cut, its two prerequisites, and how to verify one |
|
|
8
|
+
| [demo.md](demo.md) | The 30-second demo recording script |
|
|
9
|
+
| [adr/0001](adr/0001-data-source-policy.md) | Where compatibility claims may come from |
|
|
10
|
+
| [adr/0002](adr/0002-rule-severity-model.md) | What `blocker`, `risk` and `info` mean, and why they map to exit codes |
|
|
11
|
+
| [adr/0003](adr/0003-release-pipeline.md) | Why releases are tag-gated, published over OIDC, and shipped with an SBOM |
|
|
12
|
+
| [brand/guidelines.md](brand/guidelines.md) | Logo, colour, type and voice |
|
|
13
|
+
| [brand/tokens.json](brand/tokens.json) | The machine-readable palette, spacing, radii and terminal colours |
|
|
14
|
+
|
|
15
|
+
Working state — decisions, frozen interfaces, open questions — lives in
|
|
16
|
+
[../STATE.md](../STATE.md).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ADR 0003 - Release pipeline
|
|
2
2
|
|
|
3
3
|
- **Status:** accepted
|
|
4
|
-
- **Date:** 2026-08-
|
|
4
|
+
- **Date:** 2026-08-27
|
|
5
5
|
- **Context:** bunready is a CLI that people will run on their own machines, so
|
|
6
6
|
"trust the release" has to be something a stranger can verify, not a promise.
|
|
7
7
|
|
package/package.json
CHANGED
package/src/cli/args.ts
CHANGED
package/src/cli/copy.ts
CHANGED
|
@@ -8,16 +8,20 @@ import { TOOL_NAME } from "../core/version";
|
|
|
8
8
|
import type { Verdict } from "../report/types";
|
|
9
9
|
import type { Severity } from "../rules/severity";
|
|
10
10
|
|
|
11
|
+
/** Product name as it appears in user-facing copy. */
|
|
11
12
|
export const TOOL = TOOL_NAME;
|
|
12
13
|
|
|
14
|
+
/** One-line positioning statement shown with the help text. */
|
|
13
15
|
export const POSITIONING = "Know what breaks before you move a Node/TS repo to Bun.";
|
|
14
16
|
|
|
17
|
+
/** Short tagline above the usage text. */
|
|
15
18
|
export const TAGLINE = "One command. One honest verdict.";
|
|
16
19
|
|
|
17
20
|
/** `--run` executes the target's code, so the help text has to say so plainly. */
|
|
18
21
|
export const RUN_WARNING =
|
|
19
22
|
"--run executes the target's code in a temporary copy; nothing runs in place and every command is timed";
|
|
20
23
|
|
|
24
|
+
/** Full help text: usage, options and exit codes. */
|
|
21
25
|
export function helpText(version: string): string {
|
|
22
26
|
return [
|
|
23
27
|
`${TOOL} ${version}`,
|
package/src/cli/io.ts
CHANGED
package/src/cli/run.ts
CHANGED
|
@@ -12,10 +12,14 @@ import { helpText, RUN_WARNING, TOOL } from "./copy";
|
|
|
12
12
|
import { type Io, systemIo } from "./io";
|
|
13
13
|
import { colorEnabled, createTheme } from "./theme";
|
|
14
14
|
|
|
15
|
+
/** Exit code for a scan with nothing at or above the failure threshold. */
|
|
15
16
|
export const EXIT_OK = 0;
|
|
17
|
+
/** Exit code for findings at or above the failure threshold. */
|
|
16
18
|
export const EXIT_BLOCKERS = 1;
|
|
19
|
+
/** Exit code for a usage error or an incomplete scan. */
|
|
17
20
|
export const EXIT_USAGE = 2;
|
|
18
21
|
|
|
22
|
+
/** The tool version, as reported by --version. */
|
|
19
23
|
export function version(): string {
|
|
20
24
|
return TOOL_VERSION;
|
|
21
25
|
}
|
package/src/cli/theme.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface Theme {
|
|
|
17
17
|
|
|
18
18
|
const identity = (text: string): string => text;
|
|
19
19
|
|
|
20
|
+
/** Decides whether a stream's output should be coloured. */
|
|
20
21
|
export function colorEnabled(
|
|
21
22
|
env: Readonly<Record<string, string | undefined>>,
|
|
22
23
|
isTty: boolean,
|
|
@@ -30,6 +31,7 @@ export function colorEnabled(
|
|
|
30
31
|
return isTty;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
/** Builds the colour palette used by the human-readable report. */
|
|
33
35
|
export function createTheme(enabled: boolean): Theme {
|
|
34
36
|
if (!enabled) {
|
|
35
37
|
return {
|
package/src/config/baseline.ts
CHANGED
|
@@ -11,27 +11,32 @@ import type { Finding } from "../report/types";
|
|
|
11
11
|
*/
|
|
12
12
|
export const BASELINE_SCHEMA_VERSION = 1;
|
|
13
13
|
|
|
14
|
+
/** A recorded set of known findings, keyed by fingerprint. */
|
|
14
15
|
export interface Baseline {
|
|
15
16
|
readonly schemaVersion: number;
|
|
16
17
|
readonly findings: readonly string[];
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
/** What a baseline comparison found: new, known and fixed entries. */
|
|
19
21
|
export interface BaselineSummary {
|
|
20
22
|
readonly path: string;
|
|
21
23
|
readonly known: number;
|
|
22
24
|
readonly new: number;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
/** Stable identity of a finding: its rule, package and path. */
|
|
25
28
|
export function fingerprint(finding: Finding): string {
|
|
26
29
|
return [finding.id, finding.package ?? "", finding.path ?? ""].join("|");
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
/** Renders a baseline in its on-disk JSON form. */
|
|
29
33
|
export function serializeBaseline(findings: readonly Finding[]): string {
|
|
30
34
|
const fingerprints = [...new Set(findings.map(fingerprint))].sort();
|
|
31
35
|
const baseline: Baseline = { schemaVersion: BASELINE_SCHEMA_VERSION, findings: fingerprints };
|
|
32
36
|
return `${JSON.stringify(baseline, null, 2)}\n`;
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
/** Reads a baseline file, reporting malformed input as an error. */
|
|
35
40
|
export function parseBaseline(text: string, source: string): Result<Baseline> {
|
|
36
41
|
let raw: unknown;
|
|
37
42
|
try {
|
package/src/config/config.ts
CHANGED
|
@@ -7,11 +7,13 @@ import { SEVERITIES, type Severity } from "../rules/severity";
|
|
|
7
7
|
*/
|
|
8
8
|
export const CONFIG_FILENAME = "bunready.config.json";
|
|
9
9
|
|
|
10
|
+
/** Settings for the optional run phase. */
|
|
10
11
|
export interface RunConfig {
|
|
11
12
|
readonly script: string | undefined;
|
|
12
13
|
readonly maxCopyMegabytes: number;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
/** The parsed contents of the project's configuration file. */
|
|
15
17
|
export interface BunreadyConfig {
|
|
16
18
|
/** Finding ids to drop, e.g. `install/no-lockfile`. */
|
|
17
19
|
readonly ignore: readonly string[];
|
|
@@ -26,6 +28,7 @@ export interface BunreadyConfig {
|
|
|
26
28
|
readonly run: RunConfig;
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
/** Configuration used when the project has no config file. */
|
|
29
32
|
export const DEFAULT_CONFIG: BunreadyConfig = {
|
|
30
33
|
ignore: [],
|
|
31
34
|
ignorePackages: [],
|
|
@@ -35,6 +38,7 @@ export const DEFAULT_CONFIG: BunreadyConfig = {
|
|
|
35
38
|
run: { script: undefined, maxCopyMegabytes: 250 },
|
|
36
39
|
};
|
|
37
40
|
|
|
41
|
+
/** Copy limit before a project is refused as too large to run. */
|
|
38
42
|
export const DEFAULT_MAX_COPY_MEGABYTES = 250;
|
|
39
43
|
|
|
40
44
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -51,6 +55,7 @@ function configError(source: string, detail: string, hint: string): Result<Bunre
|
|
|
51
55
|
return { ok: false, error: defineError("E_PARSE", `${source} ${detail}`, { hint }) };
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
/** Parses and validates configuration, returning errors instead of throwing. */
|
|
54
59
|
export function parseConfig(text: string, source = CONFIG_FILENAME): Result<BunreadyConfig> {
|
|
55
60
|
let raw: unknown;
|
|
56
61
|
try {
|
package/src/core/errors.ts
CHANGED
|
@@ -19,24 +19,30 @@ export interface BunreadyError {
|
|
|
19
19
|
readonly cause?: unknown;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** A successful result. */
|
|
22
23
|
export type Ok<T> = { readonly ok: true; readonly value: T };
|
|
24
|
+
/** A failed result carrying an error. */
|
|
23
25
|
export type Err = { readonly ok: false; readonly error: BunreadyError };
|
|
24
26
|
|
|
25
27
|
/** Success or failure. Errors are values here, not control flow. */
|
|
26
28
|
export type Result<T> = Ok<T> | Err;
|
|
27
29
|
|
|
30
|
+
/** Wraps a value in a successful result. */
|
|
28
31
|
export function ok<T>(value: T): Ok<T> {
|
|
29
32
|
return { ok: true, value };
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
/** Wraps an error in a failed result. */
|
|
32
36
|
export function err<T = never>(error: BunreadyError): Result<T> {
|
|
33
37
|
return { ok: false, error };
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
/** Narrows a result to its success case. */
|
|
36
41
|
export function isOk<T>(result: Result<T>): result is Ok<T> {
|
|
37
42
|
return result.ok;
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
/** Narrows a result to its failure case. */
|
|
40
46
|
export function isErr<T>(result: Result<T>): result is Err {
|
|
41
47
|
return !result.ok;
|
|
42
48
|
}
|
package/src/core/fs.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface DirectoryEntry {
|
|
|
17
17
|
readonly isDirectory: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** The file operations the scanner needs, injected so tests can run in memory. */
|
|
20
21
|
export interface FileSystem {
|
|
21
22
|
readonly readTextFile: (path: string) => Promise<ReadOutcome>;
|
|
22
23
|
readonly writeTextFile: (path: string, text: string) => Promise<void>;
|
|
@@ -28,6 +29,7 @@ function describe(error: unknown): string {
|
|
|
28
29
|
return error instanceof Error ? error.message : String(error);
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/** The FileSystem implementation backed by node:fs. */
|
|
31
33
|
export function nodeFileSystem(): FileSystem {
|
|
32
34
|
return {
|
|
33
35
|
readTextFile: async (path) => {
|
package/src/core/version.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import packageJson from "../../package.json";
|
|
1
|
+
import packageJson from "../../package.json" with { type: "json" };
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Tool identity, in a module with no dependencies, so anything (CLI, scanner,
|
|
@@ -6,4 +6,5 @@ import packageJson from "../../package.json";
|
|
|
6
6
|
*/
|
|
7
7
|
export const TOOL_NAME = "bunready";
|
|
8
8
|
|
|
9
|
+
/** The tool's own version, used in reports. */
|
|
9
10
|
export const TOOL_VERSION: string = packageJson.version;
|
package/src/report/human.ts
CHANGED
|
@@ -22,6 +22,7 @@ function label(finding: Finding, theme: Theme): string {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** Renders a report for a terminal: findings by severity, then the verdict. */
|
|
25
26
|
export function renderHumanReport(report: ScanReport, theme: Theme): string {
|
|
26
27
|
const lines: string[] = [];
|
|
27
28
|
const facts: string[] = [];
|
package/src/report/sarif.ts
CHANGED
package/src/report/types.ts
CHANGED
|
@@ -29,6 +29,7 @@ export interface Finding {
|
|
|
29
29
|
readonly hint?: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** Overall judgement for a scan: blocked, risky or ready. */
|
|
32
33
|
export type Verdict = "ready" | "risky" | "blocked";
|
|
33
34
|
|
|
34
35
|
/** One scanned directory in a multi-package repository. */
|
|
@@ -88,7 +89,13 @@ export interface ScanReport {
|
|
|
88
89
|
readonly baseline?: BaselineSummary;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* The single ordering authority for findings: blockers first, then risks, then
|
|
94
|
+
* info; within a severity by rule id, then title, then the directory it came
|
|
95
|
+
* from. The tiebreakers make the order *total*, so the same repository produces
|
|
96
|
+
* byte-identical output however the findings were collected - which is what a
|
|
97
|
+
* diff in a pull request or a baseline comparison depends on.
|
|
98
|
+
*/
|
|
92
99
|
export function sortFindings(findings: readonly Finding[]): Finding[] {
|
|
93
100
|
return [...findings].sort((a, b) => {
|
|
94
101
|
const bySeverity = compareSeverity(a.severity, b.severity);
|
|
@@ -96,10 +103,15 @@ export function sortFindings(findings: readonly Finding[]): Finding[] {
|
|
|
96
103
|
return bySeverity;
|
|
97
104
|
}
|
|
98
105
|
const byId = a.id.localeCompare(b.id);
|
|
99
|
-
|
|
106
|
+
if (byId !== 0) {
|
|
107
|
+
return byId;
|
|
108
|
+
}
|
|
109
|
+
const byTitle = a.title.localeCompare(b.title);
|
|
110
|
+
return byTitle !== 0 ? byTitle : (a.path ?? "").localeCompare(b.path ?? "");
|
|
100
111
|
});
|
|
101
112
|
}
|
|
102
113
|
|
|
114
|
+
/** The judgement a set of findings amounts to. */
|
|
103
115
|
export function verdictFor(findings: readonly Finding[]): Verdict {
|
|
104
116
|
let verdict: Verdict = "ready";
|
|
105
117
|
for (const finding of findings) {
|
|
@@ -16,10 +16,12 @@ import type { TargetSnapshot } from "../../scanner/target";
|
|
|
16
16
|
* facts; nothing is inferred from package names.
|
|
17
17
|
*/
|
|
18
18
|
export const LIFECYCLE_DOC = "https://bun.com/docs/pm/lifecycle";
|
|
19
|
+
/** Explains trustedDependencies, attached to lifecycle-script findings. */
|
|
19
20
|
export const TRUSTED_DEPENDENCIES_GUIDE = "https://bun.com/guides/install/trusted";
|
|
20
21
|
|
|
21
22
|
const ID = "install/lifecycle-script";
|
|
22
23
|
|
|
24
|
+
/** Flags install scripts that will not run unless the package is trusted. */
|
|
23
25
|
export function lifecycleScriptFindings(
|
|
24
26
|
snapshot: TargetSnapshot,
|
|
25
27
|
lockfile: ParsedLockfile | undefined,
|
|
@@ -13,6 +13,7 @@ const BINARY_LOCKFILE_ID = "install/binary-lockfile";
|
|
|
13
13
|
const UNPARSED_LOCKFILE_ID = "install/unparsed-lockfile";
|
|
14
14
|
const MULTIPLE_LOCKFILES_ID = "install/multiple-lockfiles";
|
|
15
15
|
|
|
16
|
+
/** Reports lockfiles that are missing, unreadable or Bun-unfriendly. */
|
|
16
17
|
export function lockfileFindings(snapshot: TargetSnapshot): Finding[] {
|
|
17
18
|
const findings: Finding[] = [];
|
|
18
19
|
|