@mh-alikhani/bunready 0.1.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/LICENSE +21 -0
  3. package/README.md +129 -0
  4. package/action.yml +89 -0
  5. package/docs/CONFIGURATION.md +44 -0
  6. package/docs/JSON-OUTPUT.md +50 -0
  7. package/docs/RELEASING.md +65 -0
  8. package/docs/adr/0001-data-source-policy.md +36 -0
  9. package/docs/adr/0002-rule-severity-model.md +42 -0
  10. package/docs/adr/0003-release-pipeline.md +51 -0
  11. package/docs/brand/favicon.svg +8 -0
  12. package/docs/brand/guidelines.md +70 -0
  13. package/docs/brand/logo-dark.svg +11 -0
  14. package/docs/brand/logo-mono.svg +11 -0
  15. package/docs/brand/logo.svg +11 -0
  16. package/docs/brand/mark.svg +8 -0
  17. package/docs/brand/tokens.json +74 -0
  18. package/docs/demo.md +37 -0
  19. package/package.json +71 -0
  20. package/src/cli/args.ts +177 -0
  21. package/src/cli/copy.ts +76 -0
  22. package/src/cli/index.ts +5 -0
  23. package/src/cli/io.ts +20 -0
  24. package/src/cli/run.ts +98 -0
  25. package/src/cli/theme.ts +59 -0
  26. package/src/config/baseline.ts +116 -0
  27. package/src/config/config.ts +113 -0
  28. package/src/core/errors.ts +59 -0
  29. package/src/core/fs.ts +72 -0
  30. package/src/core/version.ts +9 -0
  31. package/src/report/human.ts +100 -0
  32. package/src/report/json.ts +11 -0
  33. package/src/report/sarif.ts +73 -0
  34. package/src/report/types.ts +114 -0
  35. package/src/rules/data/native-packages.json +81 -0
  36. package/src/rules/data/node-runtime.json +6 -0
  37. package/src/rules/install/engines.ts +74 -0
  38. package/src/rules/install/index.ts +27 -0
  39. package/src/rules/install/lifecycle-scripts.ts +70 -0
  40. package/src/rules/install/lockfile-presence.ts +68 -0
  41. package/src/rules/install/native-addon.ts +126 -0
  42. package/src/rules/run/index.ts +114 -0
  43. package/src/rules/runtime/builtins.ts +148 -0
  44. package/src/rules/runtime/index.ts +18 -0
  45. package/src/rules/severity.ts +46 -0
  46. package/src/scanner/execute.ts +301 -0
  47. package/src/scanner/graph.ts +77 -0
  48. package/src/scanner/lockfile.ts +545 -0
  49. package/src/scanner/manifest.ts +109 -0
  50. package/src/scanner/scan.ts +322 -0
  51. package/src/scanner/semver.ts +227 -0
  52. package/src/scanner/sources.ts +355 -0
  53. package/src/scanner/target.ts +224 -0
  54. package/src/scanner/workspaces.ts +170 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,140 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ Nothing yet.
11
+
12
+ ## [0.1.0] - 2026-09-09
13
+
14
+ ### Added
15
+
16
+ - Repository hygiene baseline: `.gitignore`, `.gitattributes`, `.editorconfig`,
17
+ `LICENSE` (MIT), `README.md`, `CONTRIBUTING.md`, `SECURITY.md`,
18
+ `CODE_OF_CONDUCT.md`.
19
+ - Git hooks via `simple-git-hooks`: `pre-commit` (Biome on staged files +
20
+ project typecheck) and `commit-msg` (commitlint, Conventional Commits).
21
+ - Brand system under `docs/brand/`: design tokens, logo variants, favicon,
22
+ guidelines, and voice/copy reference.
23
+ - Project scaffold: strict ESM `tsconfig.json`, Biome 2.x config, `bun:test`
24
+ suite, and the `src/{cli,core,rules,report}` layout.
25
+ - CLI skeleton (`src/cli/`): argument parsing, `--help`, `--version`, `--json`,
26
+ `NO_COLOR` handling, and the documented exit-code contract
27
+ (`0` clean, `1` blockers, `2` usage error).
28
+ - `src/core/errors.ts`: a `Result` type and an actionable error shape with a
29
+ `hint` field, so failures cross module boundaries as values rather than
30
+ thrown exceptions.
31
+ - Architecture decision records `0001` (data source policy) and `0002` (rule
32
+ severity model).
33
+ - **Scanning.** `bunready <path>` reads `package.json` and the lockfile and
34
+ prints a real report. The dependency graph is built from `bun.lock`,
35
+ `package-lock.json`, `yarn.lock` or `pnpm-lock.yaml`.
36
+ - Hand-written lockfile parsers (`src/scanner/lockfile.ts`), covering Bun's JSONC
37
+ text lockfile, npm v1/v2/v3, Yarn v1 and Berry, and pnpm. A lockfile that
38
+ cannot be parsed is reported as a finding instead of silently yielding an
39
+ empty graph.
40
+ - Install-phase rules (`src/rules/install/`): blocked lifecycle scripts
41
+ (`blocker`), native addons (`risk`), `engines` conflicts, and evidence gaps
42
+ such as a binary `bun.lockb` or a missing lockfile.
43
+ - Vendored native-package dataset (`src/rules/data/native-packages.json`); every
44
+ entry carries a source link, and entries assert a property of the package
45
+ rather than a Bun compatibility claim.
46
+ - Minimal semver range evaluation (`src/scanner/semver.ts`) for `engines`, which
47
+ reports "could not evaluate" instead of guessing.
48
+ - Report renderers: a human report (`src/report/human.ts`) and `--json`
49
+ (`src/report/json.ts`), with `ScanReport.stats` for context behind a verdict.
50
+ - Runtime-phase rule (`src/rules/runtime/`): the Node built-in modules the
51
+ repository's own code imports, reported as an inventory with a link to Bun's
52
+ compatibility table rather than as a verdict bunready cannot source.
53
+ - Import scanning (`src/scanner/sources.ts`): a regex-based extractor that masks
54
+ strings and comments first, so a fixture containing import-shaped text is not
55
+ counted as an import. The walk skips `node_modules` and build output, and
56
+ reports when it hits its file cap.
57
+ - `ScanReport.stats` now also carries the source-file count and the number of
58
+ Node built-ins found.
59
+ - Continuous integration: `.github/workflows/ci.yml` runs lint, typecheck, tests
60
+ with coverage and a build on Ubuntu, macOS and Windows, against both the
61
+ current Bun release and the exact floor `engines.bun` claims.
62
+ - `.github/workflows/security.yml`: gitleaks secret scanning, CodeQL code
63
+ scanning (`security-and-quality`) and `bun audit`, on pushes, pull requests and
64
+ a weekly schedule.
65
+ - `.github/dependabot.yml`: weekly updates for dev dependencies and for GitHub
66
+ Actions, so the SHA pins stay current.
67
+ - Live CI and security badges in the README.
68
+ - Release pipeline (`.github/workflows/release.yml`): a `v*` tag asserts CI is
69
+ green on that exact commit, compiles and smoke-tests Linux, macOS and Windows
70
+ binaries, publishes to npm with provenance over OIDC, and attaches the
71
+ binaries, `SHA256SUMS` and a CycloneDX SBOM to the GitHub release.
72
+ - `scripts/generate-sbom.ts` and `scripts/checksums.ts`, with the pure logic in
73
+ `scripts/lib/` so the artifact formats are tested rather than trusted.
74
+ - `docs/RELEASING.md` (process and prerequisites) and
75
+ `docs/adr/0003-release-pipeline.md` (why OIDC, binaries and a tag gate).
76
+ - `--run`: the target is copied to a temporary directory, dependencies are
77
+ installed there and its `start` (or `test`) script is booted with `bun run`.
78
+ The first real failure is captured with its stack frames. Nothing is executed
79
+ in place, every command is timed, and the copy is removed even when the run
80
+ fails.
81
+ - `bunready.config.json` (`src/config/`): ignore findings by rule id or package,
82
+ allowlist native addons, exclude paths, raise or lower `failOn`, and choose the
83
+ script and copy limit `--run` uses. `--config` points elsewhere; a missing file
84
+ is an error rather than a silent default. See `docs/CONFIGURATION.md`.
85
+ - `--json` now carries `schemaVersion` and `failOn`, so a CI job can pin the
86
+ contract and see which threshold produced the exit code.
87
+ See `docs/JSON-OUTPUT.md`.
88
+ - `--sarif` writes SARIF 2.1.0 for code-scanning upload, generated from the same
89
+ findings as every other renderer.
90
+ - `--run-script <name>` picks the script to boot, and `--run` refuses a target
91
+ larger than `run.maxCopyMegabytes` instead of silently skipping it.
92
+ - Workspace support: `workspaces` in package.json or a `pnpm-workspace.yaml` is
93
+ detected, every package is scanned, the report aggregates them with a `targets`
94
+ list and a `path` on each finding, and `--scope` narrows a scan to the matching
95
+ packages. Globs are expanded with a small, documented matcher.
96
+ - Baseline and regression detection: `--write-baseline` records the findings you
97
+ have accepted, `--baseline` compares against them, marks the rest `new` and
98
+ fails the run only for those. The fingerprint is rule + package + path.
99
+ - `action.yml`: a composite GitHub Action that scans a repository, uploads the
100
+ SARIF report to code scanning and fails the step on findings at or above
101
+ `failOn`. `version: local` runs the action from source, which is how CI tests
102
+ it before the first publish.
103
+
104
+ ### Fixed
105
+
106
+ - `.github/workflows/release.yml` was invalid YAML: a secrets expression had been
107
+ written as a literal `*` alias, and GitHub does not fail loudly for that - it
108
+ simply refuses to run the workflow. `ci.yml` now parses every workflow file on
109
+ each pull request so the class of break cannot reach `main` again.
110
+ - `actions/checkout` to v7, `gitleaks/gitleaks-action` to v3, `github/codeql-action`
111
+ init and analyze to v4 (they must move together), `@commitlint/*` to 21 and
112
+ TypeScript to 7, with a regenerated lockfile.
113
+ - `engines.bun` now declares `>=1.4.0`. The previous `>=1.2.0` claim was wrong:
114
+ the committed `bun.lock` is text lockfile format version 2, which Bun 1.2 and
115
+ 1.3 reject with an unknown-lockfile-version error. The CI matrix caught it on
116
+ its first run.
117
+ - Scanning bunready itself now exits `0`. The `install/lifecycle-script` finding
118
+ about `simple-git-hooks` was true, so the fix was to list it in
119
+ `trustedDependencies` rather than to weaken the rule.
120
+ - Workspace glob expansion: `packages/*` and `packages/**` are expanded against
121
+ the correct parent directory, bounded in depth, and never walk `node_modules`.
122
+ `finding.path` is also normalised to forward slashes on every platform.
123
+
124
+ ### Changed
125
+
126
+ - A scan now produces a verdict and an exit code.
127
+ - `--run` executes the target's own script in a temporary copy instead of
128
+ reporting itself as unimplemented.
129
+ - Paths in findings print with forward slashes, so output is identical on every
130
+ operating system.
131
+
132
+ ### Notes
133
+
134
+ - `src/rules/data/node-runtime.json` ships an empty `gaps` list on purpose: each
135
+ entry would assert that a specific Node built-in is partial or missing in Bun,
136
+ and that claim needs a primary source. Until then the rule reports what the
137
+ repository imports and cites the compatibility table.
138
+
139
+ [Unreleased]: https://github.com/MHAlikhani/bunready/compare/v0.1.0...HEAD
140
+ [0.1.0]: https://github.com/MHAlikhani/bunready/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohammad Hosein Alikhani
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ <div align="center">
2
+
3
+ <img src="docs/brand/logo.svg" alt="bunready" width="360">
4
+
5
+ **One command that tells you what will break before you move a Node/TS repo to Bun — and gives you one clear verdict.**
6
+
7
+ [![ci](https://github.com/MHAlikhani/bunready/actions/workflows/ci.yml/badge.svg)](https://github.com/MHAlikhani/bunready/actions/workflows/ci.yml)
8
+ [![security](https://github.com/MHAlikhani/bunready/actions/workflows/security.yml/badge.svg)](https://github.com/MHAlikhani/bunready/actions/workflows/security.yml)
9
+ [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
10
+ [![runtime](https://img.shields.io/badge/runtime-Bun%20%E2%89%A5%201.4-black)](https://bun.sh)
11
+ [![types](https://img.shields.io/badge/TypeScript-strict-3178c6)](tsconfig.json)
12
+ [![status](https://img.shields.io/badge/status-pre--alpha-orange)](#status)
13
+
14
+ [![npm](https://img.shields.io/npm/v/@mh-alikhani/bunready)](https://www.npmjs.com/package/@mh-alikhani/bunready)
15
+ -->
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## Status
22
+
23
+ Pre-alpha, and precise about it. `bunready <path>` reads `package.json`, any
24
+ lockfile, and the repository's own imports, then prints a report with a verdict
25
+ and an exit code CI can gate on.
26
+
27
+ What it does not do yet: `--run` (executing the target's scripts under Bun) is
28
+ accepted and reported as unimplemented, and no Node-runtime gap is claimed
29
+ without a source. See [STATE.md](STATE.md) for what exists and what does not.
30
+
31
+ ## Install
32
+
33
+ ```sh
34
+ bunx @mh-alikhani/bunready . # run without installing
35
+ bun add --global @mh-alikhani/bunready
36
+ ```
37
+
38
+ Or from a checkout:
39
+
40
+ ```sh
41
+ git clone https://github.com/MHAlikhani/bunready.git
42
+ cd bunready
43
+ bun install
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ```sh
49
+ bunx @mh-alikhani/bunready --help
50
+
51
+ # against a target repository
52
+ bunx @mh-alikhani/bunready /path/to/node-project
53
+ ```
54
+
55
+ | Flag | Meaning |
56
+ | --- | --- |
57
+ | `--help` | Print usage and exit. |
58
+ | `--version` | Print the CLI version and exit. |
59
+ | `--json` | Emit machine-readable JSON instead of the terminal report. |
60
+ | `--run` | Planned: run the target's own scripts under Bun in a temporary copy. Not implemented yet; the flag says so before scanning. |
61
+ | `NO_COLOR` | Environment variable: disable ANSI color when set. |
62
+
63
+ Exit codes: `0` no blockers, `1` blockers found, `2` usage error.
64
+
65
+ ## Monorepos
66
+
67
+ A `workspaces` field in `package.json`, or a `pnpm-workspace.yaml`, is detected
68
+ automatically: the root and every package are scanned, and the report aggregates
69
+ them with a `targets` list and a `path` on each finding. `--scope packages/api`
70
+ narrows a scan to the matching packages.
71
+
72
+ ## Baselines
73
+
74
+ ```sh
75
+ bunready . --write-baseline bunready.baseline.json # accept today's findings
76
+ bunready . --baseline bunready.baseline.json # fail only on new ones
77
+ ```
78
+
79
+ A baseline records rule, package and path - not the message - so rewording a
80
+ finding does not resurrect it.
81
+
82
+ ## In CI
83
+
84
+ ```yaml
85
+ permissions:
86
+ contents: read
87
+ security-events: write # required for the SARIF upload
88
+
89
+ steps:
90
+ - uses: actions/checkout@v7
91
+ - uses: MHAlikhani/bunready@v0.1.0
92
+ with:
93
+ path: .
94
+ ```
95
+
96
+ The action writes a JSON report, uploads the SARIF report to code scanning and
97
+ fails the step when findings at or above `failOn` exist. Inputs: `path`,
98
+ `version` (`latest` or `local`), `sarif-file`, `json-file`, `upload`.
99
+
100
+ ## Why
101
+
102
+ Moving a repo to Bun is usually a pile of small unknowns: which npm lifecycle
103
+ scripts actually run, which APIs are partial, which packages are native, which
104
+ test runner behaviours differ. bunready answers that with evidence and a single
105
+ verdict instead of a checklist you have to interpret yourself.
106
+
107
+ ## Compatibility data policy
108
+
109
+ Compatibility claims come only from public Bun documentation and issue tracker
110
+ entries, shipped as versioned JSON with source links. We never invent
111
+ compatibility facts. See [ADR 0001](docs/adr/0001-data-source-policy.md).
112
+
113
+ ## Contributing
114
+
115
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and the
116
+ [Code of Conduct](CODE_OF_CONDUCT.md). Security reports go through
117
+ [SECURITY.md](SECURITY.md).
118
+
119
+ ## Disclaimer
120
+
121
+ bunready is an independent, community project. It is **not affiliated with,
122
+ endorsed by, or sponsored by the Bun project, Oven**. "Bun" and
123
+ the Bun logo are trademarks of their respective owners and are used here only
124
+ for descriptive, nominative purposes. bunready ships no Bun code and no Bun
125
+ branding.
126
+
127
+ ## License
128
+
129
+ [MIT](LICENSE) © Mohammad Hosein Alikhani
package/action.yml ADDED
@@ -0,0 +1,89 @@
1
+ name: bunready
2
+ description: Report what will break when a Node or TypeScript repository moves to Bun, and upload the result to code scanning.
3
+ author: MHAlikhani
4
+ branding:
5
+ icon: check-circle
6
+ color: orange
7
+
8
+ inputs:
9
+ path:
10
+ description: Repository to scan.
11
+ required: false
12
+ default: "."
13
+ version:
14
+ description: bunready version to run from npm, or "local" to use the source inside this action.
15
+ required: false
16
+ default: latest
17
+ sarif-file:
18
+ description: Where to write the SARIF report.
19
+ required: false
20
+ default: bunready.sarif
21
+ json-file:
22
+ description: Where to write the JSON report.
23
+ required: false
24
+ default: bunready.json
25
+ upload:
26
+ description: Upload the SARIF report to GitHub code scanning.
27
+ required: false
28
+ default: "true"
29
+
30
+ outputs:
31
+ exit-code:
32
+ description: 0 when nothing at or above failOn was found, 1 otherwise.
33
+ value: ${{ steps.scan.outputs.exit-code }}
34
+ verdict:
35
+ description: ready, risky or blocked.
36
+ value: ${{ steps.scan.outputs.verdict }}
37
+
38
+ runs:
39
+ using: composite
40
+ steps:
41
+ - name: Set up Bun
42
+ uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
43
+ with:
44
+ bun-version: latest
45
+
46
+ # The scan itself never fails the step: the SARIF report has to be uploaded
47
+ # before the exit code is honoured, or code scanning would never see the
48
+ # findings of a failing run.
49
+ - name: Scan
50
+ id: scan
51
+ shell: bash
52
+ env:
53
+ INPUT_VERSION: ${{ inputs.version }}
54
+ INPUT_PATH: ${{ inputs.path }}
55
+ INPUT_SARIF: ${{ inputs.sarif-file }}
56
+ INPUT_JSON: ${{ inputs.json-file }}
57
+ run: |
58
+ set -euo pipefail
59
+
60
+ if [ "${INPUT_VERSION}" = "local" ]; then
61
+ scan() { bun run "${GITHUB_ACTION_PATH}/src/cli/index.ts" "$@"; }
62
+ else
63
+ scan() { bunx --bun "@mh-alikhani/bunready@${INPUT_VERSION}" "$@"; }
64
+ fi
65
+
66
+ set +e
67
+ scan "${INPUT_PATH}" --json > "${INPUT_JSON}"
68
+ status=$?
69
+ set -e
70
+
71
+ verdict=$(node -e 'const fs=require("node:fs");process.stdout.write(JSON.parse(fs.readFileSync(process.argv[1],"utf8")).verdict)' "${INPUT_JSON}")
72
+
73
+ printf 'exit-code=%s\n' "${status}" >> "${GITHUB_OUTPUT}"
74
+ printf 'verdict=%s\n' "${verdict}" >> "${GITHUB_OUTPUT}"
75
+ printf 'bunready: %s (exit %s)\n' "${verdict}" "${status}" >> "${GITHUB_STEP_SUMMARY}"
76
+
77
+ scan "${INPUT_PATH}" --sarif > "${INPUT_SARIF}"
78
+
79
+ - name: Upload SARIF
80
+ if: ${{ inputs.upload == 'true' }}
81
+ uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4
82
+ with:
83
+ sarif_file: ${{ inputs.sarif-file }}
84
+ category: bunready
85
+
86
+ - name: Fail when bunready reported findings at or above failOn
87
+ if: ${{ steps.scan.outputs.exit-code != '0' }}
88
+ shell: bash
89
+ run: exit 1
@@ -0,0 +1,44 @@
1
+ # Configuration
2
+
3
+ `bunready.config.json` in the scanned repository root. Every key is optional; the
4
+ defaults are what you get when the file is absent. `--config <path>` points at a
5
+ different file, and a path that does not exist is an error rather than a silent
6
+ fallback.
7
+
8
+ ```json
9
+ {
10
+ "ignore": ["install/no-lockfile"],
11
+ "ignorePackages": ["fsevents"],
12
+ "nativeAllowlist": ["sharp"],
13
+ "excludePaths": ["fixtures/"],
14
+ "failOn": "risk",
15
+ "run": { "script": "test", "maxCopyMegabytes": 100 }
16
+ }
17
+ ```
18
+
19
+ | Key | Type | Default | Effect |
20
+ | --- | --- | --- | --- |
21
+ | `ignore` | string[] | `[]` | Drops findings by rule id, e.g. `install/no-lockfile`. |
22
+ | `ignorePackages` | string[] | `[]` | Drops findings whose `package` field names one of these dependencies. |
23
+ | `nativeAllowlist` | string[] | `[]` | Packages the native-addon rule never reports. |
24
+ | `excludePaths` | string[] | `[]` | Substring match against source paths; a match skips the file in the import scan. |
25
+ | `failOn` | `blocker` \| `risk` \| `info` | `blocker` | Lowest severity that makes the process exit `1`. |
26
+ | `run.script` | string | first of `start`, `test` | Script booted by `--run`. |
27
+ | `run.maxCopyMegabytes` | number | `250` | Refuses to copy a repository larger than this. Reported as a `risk`, never a silent skip. |
28
+
29
+ ## What configuration cannot do
30
+
31
+ It cannot change what a rule *claims*. `ignore` and `ignorePackages` suppress a
32
+ finding you have decided is acceptable; they do not rewrite the evidence, and the
33
+ report still exits `0` only because you said so. That is the intended trade: the
34
+ tool stays honest about the repository, and the repository stays honest about
35
+ what it has accepted.
36
+
37
+ `failOn` is echoed in the `--json` report as `failOn`, so a CI log always shows
38
+ which threshold produced the exit code.
39
+
40
+ ## Precedence
41
+
42
+ 1. CLI flags (`--run-script`, `--config`)
43
+ 2. `bunready.config.json` in the target
44
+ 3. Built-in defaults
@@ -0,0 +1,50 @@
1
+ # JSON output
2
+
3
+ `bunready <path> --json` writes a `ScanReport` to stdout and nothing else, so it
4
+ can be piped straight into `jq` or a CI step.
5
+
6
+ ```jsonc
7
+ {
8
+ "schemaVersion": 1, // bumped only for a breaking change
9
+ "failOn": "blocker", // the threshold that decides the exit code
10
+ "tool": "bunready",
11
+ "version": "0.1.0",
12
+ "target": "/path/to/repo",
13
+ "verdict": "blocked", // ready | risky | blocked
14
+ "counts": { "blocker": 1, "risk": 0, "info": 1 },
15
+ "findings": [
16
+ {
17
+ "id": "install/lifecycle-script", // stable rule id
18
+ "severity": "blocker", // blocker | risk | info
19
+ "title": "…",
20
+ "detail": "…",
21
+ "package": "sharp", // present when the finding is about one dependency
22
+ "evidence": "…", // what was observed, never a guess
23
+ "hint": "…", // the next step
24
+ "source": "https://bun.com/docs/pm/lifecycle" // required for compat claims
25
+ }
26
+ ],
27
+ "stats": { "…": "what the scan looked at" },
28
+ "run": { "script": "test", "exitCode": 0 } // only with --run
29
+ }
30
+ ```
31
+
32
+ ## Compatibility
33
+
34
+ `schemaVersion` is the contract. A patch or minor release of bunready never
35
+ changes the meaning of an existing field; a field may be added, and consumers
36
+ should ignore unknown fields. A rename or removal bumps `schemaVersion`.
37
+
38
+ ## Exit codes
39
+
40
+ | Code | Meaning |
41
+ | --- | --- |
42
+ | `0` | Nothing at or above `failOn`. |
43
+ | `1` | At least one finding at or above `failOn`. |
44
+ | `2` | Usage error, or the scan could not complete (no `--json` is written in this case). |
45
+
46
+ ## Machine-readable variants
47
+
48
+ - `--json` - this document.
49
+ - `--sarif` - SARIF 2.1.0 for code-scanning upload. The two are mutually
50
+ exclusive; passing both is a usage error rather than an ambiguous stream.
@@ -0,0 +1,65 @@
1
+ # Releasing
2
+
3
+ Two things must be true before a release can happen, and one of them is not in
4
+ this repository.
5
+
6
+ ## Prerequisites
7
+
8
+ 1. **CI is green on the commit you are tagging.** The release workflow checks
9
+ this itself and fails closed: it queries the `ci` workflow for a successful
10
+ run on exactly that SHA. If CI is red or has not run, there is no release.
11
+ 2. **Trusted publishing is configured on npmjs.com** (owner action, done once).
12
+ Open the `@mh-alikhani/bunready` package settings on npmjs.com, add a trusted publisher
13
+ pointing at this repository, the `release.yml` workflow file, and the `release`
14
+ environment if you use one. Without this, the publish job fails with an
15
+ authentication error — by design, because the alternative would be a
16
+ long-lived token in the repository.
17
+
18
+ No `NPM_TOKEN` secret exists or is needed. Authentication is OIDC.
19
+
20
+ ## Cutting a release
21
+
22
+ ```sh
23
+ # 1. Make sure main is where you want it, and CI is green
24
+ gh run list --workflow ci.yml --limit 3
25
+
26
+ # 2. Bump the version and move the changelog entry out of Unreleased
27
+ # (package.json + CHANGELOG.md), then land it through a pull request
28
+
29
+ # 3. Tag the release commit and push the tag
30
+ git tag -a v0.1.0 -m "bunready v0.1.0"
31
+ git push origin v0.1.0
32
+ ```
33
+
34
+ The tag is the release. `release.yml` then:
35
+
36
+ | Step | What it does |
37
+ | --- | --- |
38
+ | `verify` | Refuses to continue unless `ci` succeeded on this SHA. |
39
+ | `binaries` | Compiles and smoke-tests `bunready-linux-x64`, `bunready-darwin-arm64`, `bunready-windows-x64.exe`. |
40
+ | `publish` | `npm publish --provenance --access public` over OIDC. |
41
+ | `release` | Generates `bom.json` (CycloneDX) and `SHA256SUMS`, then attaches all five files to the GitHub release with generated notes. |
42
+
43
+ ## Verifying a release as a user
44
+
45
+ ```sh
46
+ gh release download v0.1.0 --repo MHAlikhani/bunready
47
+ sha256sum -c SHA256SUMS
48
+ ```
49
+
50
+ `bom.json` lists every locked package with a `purl`, so the dependency set behind
51
+ a published version can be read without cloning anything.
52
+
53
+ ## Versioning
54
+
55
+ Semantic versioning. Until `1.0.0`, a minor bump means new rules, a patch bump
56
+ means fixes to existing behaviour, and any change to the CLI contract (exit
57
+ codes, `--json` fields) gets called out under `### Changed`.
58
+
59
+ ## What is deliberately missing
60
+
61
+ - **Code signing.** macOS and Windows binaries are unsigned for `v0.1.0`; users
62
+ will see Gatekeeper and SmartScreen warnings. Signing needs certificates and a
63
+ budget decision, not just code.
64
+ - **A coverage badge.** Auto-committing one would require write access to
65
+ protected `main`; see D23 in STATE.md.
@@ -0,0 +1,36 @@
1
+ # ADR 0001 - Compatibility data source policy
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-09-15
5
+ - **Context:** `bunready`'s only value is that its verdict can be trusted.
6
+
7
+ ## Decision
8
+
9
+ Compatibility claims ship as versioned JSON in `src/rules/data/`, and every
10
+ entry carries `source`, a link to the public Bun documentation or issue that
11
+ establishes the claim. The dataset records the Bun version range it was
12
+ validated against.
13
+
14
+ Rules may also report **observed facts about the scanned repository** (a native
15
+ addon is present, a lifecycle script exists, an import resolves to a Node
16
+ built-in). Those need no external source: the evidence is the repo itself and is
17
+ printed with the finding.
18
+
19
+ If a claim has neither a source link nor repo evidence, it does not ship.
20
+
21
+ ## Consequences
22
+
23
+ - The dataset is reviewable: a reviewer can open every link.
24
+ - Adding a rule requires a source, which keeps rule authorship slow on purpose.
25
+ - The dataset is versioned with the code; the report states which dataset
26
+ version produced the verdict, so an old verdict stays explainable.
27
+
28
+ ## Rejected alternatives
29
+
30
+ - **Bundling an upstream compatibility list without links.** Unverifiable, ages
31
+ badly, and makes us the authority on someone else's runtime.
32
+ - **Asking the network at scan time.** `bunready` must work offline and must
33
+ never phone home. Data is vendored, not fetched.
34
+ - **Inferring compatibility from package metadata alone.** Would produce
35
+ confident-sounding guesses; the guardrail is "never invent compatibility
36
+ facts".
@@ -0,0 +1,42 @@
1
+ # ADR 0002 - Rule severity model
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-09-15
5
+ - **Context:** The report has to be actionable, and CI has to be able to gate on
6
+ it, without turning a migration into a wall of red.
7
+
8
+ ## Decision
9
+
10
+ Every finding carries exactly one severity:
11
+
12
+ | Severity | Meaning | Exit code effect |
13
+ | --- | --- | --- |
14
+ | `blocker` | The target repo cannot run correctly on Bun until this is fixed. | Process exits `1`. |
15
+ | `risk` | A real hazard that needs a human judgement call; may still be fine. | No effect. |
16
+ | `info` | Context that helps the reader decide. | No effect. |
17
+
18
+ Exit codes are fixed: `0` no blockers, `1` blockers found, `2` usage error or
19
+ incomplete scan.
20
+
21
+ Severity is decided by one question: **if the user does nothing, does this break
22
+ at runtime or at install time?** Yes, and unavoidable -> `blocker`. Yes, but
23
+ conditional on how the code is used -> `risk`. No -> `info`.
24
+
25
+ Colour is presentation only. Every severity prints as a word, so `NO_COLOR`,
26
+ monochrome terminals, and log files keep the full meaning.
27
+
28
+ ## Consequences
29
+
30
+ - CI gating is a one-line rule (`exit != 0`) and cannot be accidentally
31
+ configured into a permanent failure by a `risk`.
32
+ - `blocker` is a strong claim, so the bar for assigning it is deliberately high.
33
+ - The severity of every rule must be justified against the single question above
34
+ in review.
35
+
36
+ ## Rejected alternatives
37
+
38
+ - **A numeric score.** Implies precision the model does not have, and invites
39
+ threshold arguments instead of reading the findings.
40
+ - **`error` / `warning`.** Borrows linter vocabulary without the run-time
41
+ meaning this tool is about.
42
+ - **Letting `risk` fail CI.** Would make the tool unusable on any real repo.
@@ -0,0 +1,51 @@
1
+ # ADR 0003 - Release pipeline
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-08-26
5
+ - **Context:** bunready is a CLI that people will run on their own machines, so
6
+ "trust the release" has to be something a stranger can verify, not a promise.
7
+
8
+ ## Decision
9
+
10
+ A release is a tag. Pushing `v*` starts `.github/workflows/release.yml`, which:
11
+
12
+ 1. **Asserts CI is green on the tagged commit.** A tag can point at anything, so
13
+ the pipeline queries the `ci` workflow for a successful run on exactly that
14
+ SHA and refuses to continue otherwise.
15
+ 2. **Publishes to npm with provenance** using OIDC (`id-token: write`) instead of
16
+ a long-lived `NPM_TOKEN`. npm signs a statement that the published bytes came
17
+ from this repository at this commit, verifiable on the registry.
18
+ 3. **Builds a compiled binary per platform** (`bun build --compile` for
19
+ linux-x64, darwin-arm64, windows-x64) and smoke-tests each one by running
20
+ `--version` before it is allowed to become an artifact. An unsigned binary
21
+ that does not run is not a release artifact.
22
+ 4. **Publishes an SBOM and checksums.** `dist/bom.json` is CycloneDX 1.5,
23
+ generated from `bun.lock` with bunready's own parser; `dist/SHA256SUMS` uses
24
+ the `sha256sum` format so verification needs no special tooling.
25
+
26
+ Every action is pinned to a commit SHA. The only job with write access to the
27
+ repository is the one attaching files to the release; only the publish job can
28
+ mint an OIDC token.
29
+
30
+ ## Consequences
31
+
32
+ - There is no npm token in this repository to leak or rotate.
33
+ - The first release requires one manual step outside the repository: registering
34
+ the trusted publisher on npmjs.com (recorded as O10 in STATE.md). Until that is
35
+ done, the publish job fails closed rather than publishing anonymously.
36
+ - Binaries are unsigned. Users on macOS and Windows will see the usual warnings;
37
+ code signing is deliberately out of scope for `v0.1.0`.
38
+ - The SBOM inherits the lockfile parser's correctness. That is intentional: the
39
+ same parser produces the scanner's dependency graph, so a parser bug shows up
40
+ in both places instead of hiding in one.
41
+
42
+ ## Rejected alternatives
43
+
44
+ - **Publish on every push to main.** No review point, and a bad commit becomes a
45
+ bad published version with no way back.
46
+ - **A long-lived `NPM_TOKEN` secret.** Simplest to set up and the most valuable
47
+ thing in the repository to steal.
48
+ - **Ship TypeScript only.** The `bin` needs a runtime with TypeScript support;
49
+ compiled binaries are what a user with no Bun installed can actually try.
50
+ - **A hand-written Release notes file.** `--generate-notes` keeps the changelog
51
+ in the commits, where CI already enforces its shape.