@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.
- package/CHANGELOG.md +140 -0
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/action.yml +89 -0
- package/docs/CONFIGURATION.md +44 -0
- package/docs/JSON-OUTPUT.md +50 -0
- package/docs/RELEASING.md +65 -0
- package/docs/adr/0001-data-source-policy.md +36 -0
- package/docs/adr/0002-rule-severity-model.md +42 -0
- package/docs/adr/0003-release-pipeline.md +51 -0
- package/docs/brand/favicon.svg +8 -0
- package/docs/brand/guidelines.md +70 -0
- package/docs/brand/logo-dark.svg +11 -0
- package/docs/brand/logo-mono.svg +11 -0
- package/docs/brand/logo.svg +11 -0
- package/docs/brand/mark.svg +8 -0
- package/docs/brand/tokens.json +74 -0
- package/docs/demo.md +37 -0
- package/package.json +71 -0
- package/src/cli/args.ts +177 -0
- package/src/cli/copy.ts +76 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/io.ts +20 -0
- package/src/cli/run.ts +98 -0
- package/src/cli/theme.ts +59 -0
- package/src/config/baseline.ts +116 -0
- package/src/config/config.ts +113 -0
- package/src/core/errors.ts +59 -0
- package/src/core/fs.ts +72 -0
- package/src/core/version.ts +9 -0
- package/src/report/human.ts +100 -0
- package/src/report/json.ts +11 -0
- package/src/report/sarif.ts +73 -0
- package/src/report/types.ts +114 -0
- package/src/rules/data/native-packages.json +81 -0
- package/src/rules/data/node-runtime.json +6 -0
- package/src/rules/install/engines.ts +74 -0
- package/src/rules/install/index.ts +27 -0
- package/src/rules/install/lifecycle-scripts.ts +70 -0
- package/src/rules/install/lockfile-presence.ts +68 -0
- package/src/rules/install/native-addon.ts +126 -0
- package/src/rules/run/index.ts +114 -0
- package/src/rules/runtime/builtins.ts +148 -0
- package/src/rules/runtime/index.ts +18 -0
- package/src/rules/severity.ts +46 -0
- package/src/scanner/execute.ts +301 -0
- package/src/scanner/graph.ts +77 -0
- package/src/scanner/lockfile.ts +545 -0
- package/src/scanner/manifest.ts +109 -0
- package/src/scanner/scan.ts +322 -0
- package/src/scanner/semver.ts +227 -0
- package/src/scanner/sources.ts +355 -0
- package/src/scanner/target.ts +224 -0
- package/src/scanner/workspaces.ts +170 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="bunready">
|
|
2
|
+
<title>bunready</title>
|
|
3
|
+
<rect width="64" height="64" rx="15" fill="#171A1F"/>
|
|
4
|
+
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
5
|
+
<path d="M25 20 L41 32 L25 44" stroke="#FBFAF7" stroke-width="5.5"/>
|
|
6
|
+
<path d="M26 50.5 H41" stroke="#D9944F" stroke-width="5.5"/>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# bunready brand guidelines
|
|
2
|
+
|
|
3
|
+
One page. If a rule is not here, the default is "quieter".
|
|
4
|
+
|
|
5
|
+
## Positioning
|
|
6
|
+
|
|
7
|
+
**bunready** — _Know what breaks before you move a Node/TS repo to Bun._
|
|
8
|
+
One command. One honest verdict.
|
|
9
|
+
|
|
10
|
+
## Voice
|
|
11
|
+
|
|
12
|
+
Calm, precise, non-alarmist. State the fact, then the next step. Four rules:
|
|
13
|
+
|
|
14
|
+
1. **Never alarm.** No `DANGER`, no `CRITICAL`, no exclamation marks.
|
|
15
|
+
2. **Never be vague.** "2 native addons will not build" beats "some issues found".
|
|
16
|
+
3. **Always give the next step.** Every error carries a `hint`.
|
|
17
|
+
4. **Never claim what was not observed.** A finding needs repo evidence or a
|
|
18
|
+
source link; if there is neither, it does not ship. See
|
|
19
|
+
[ADR 0001](adr/0001-data-source-policy.md).
|
|
20
|
+
|
|
21
|
+
Severity is a decision, not a feeling — see
|
|
22
|
+
[ADR 0002](adr/0002-rule-severity-model.md). Colour never carries meaning on its
|
|
23
|
+
own: every severity also prints as a word.
|
|
24
|
+
|
|
25
|
+
## Logo
|
|
26
|
+
|
|
27
|
+
| Asset | Use |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `docs/brand/logo.svg` | Default lockup, light backgrounds. |
|
|
30
|
+
| `docs/brand/logo-dark.svg` | Lockup on dark backgrounds. |
|
|
31
|
+
| `docs/brand/logo-mono.svg` | Single-colour lockup; inherits `currentColor`. |
|
|
32
|
+
| `docs/brand/mark.svg` | Mark alone; inherits `currentColor`. |
|
|
33
|
+
| `docs/brand/favicon.svg` | Favicon: mark on ink, tuned for 16px. |
|
|
34
|
+
| `assets/og.png` | Social card, 1200x630, generated by `scripts/generate-og-image.ts`. |
|
|
35
|
+
|
|
36
|
+
The mark is a terminal prompt — a caret and an underscore inside a rounded
|
|
37
|
+
square: "you are about to run something".
|
|
38
|
+
|
|
39
|
+
**Clear space:** 0.5 x the mark height on all four sides. Nothing enters it.
|
|
40
|
+
**Minimum sizes:** mark 24px; lockup 120px wide.
|
|
41
|
+
**Never:** recolour outside `tokens.json`, stretch, rotate, add effects,
|
|
42
|
+
place the lockup on a busy photo, or crop the mark's rounded square.
|
|
43
|
+
|
|
44
|
+
## Colour and type
|
|
45
|
+
|
|
46
|
+
`docs/brand/tokens.json` is the single source of truth: palette (light/dark),
|
|
47
|
+
severity colours, terminal ANSI-256 indexes, spacing, radii, type scale, fonts.
|
|
48
|
+
Terminal output must be legible on light and dark schemes.
|
|
49
|
+
|
|
50
|
+
## Exact copy
|
|
51
|
+
|
|
52
|
+
`src/cli/copy.ts` is the source of truth for strings, so the guidelines and the
|
|
53
|
+
binary cannot drift.
|
|
54
|
+
|
|
55
|
+
| Surface | Copy |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `--help` header | `bunready <version>` then the positioning line |
|
|
58
|
+
| Usage error | `<code>: <message>` + ` hint: <next step>` (exit 2) |
|
|
59
|
+
| Success (no blockers) | `ready - no Bun compatibility blockers found` |
|
|
60
|
+
| Blocked | `blocked - N blocker(s) must be fixed before this repo runs on Bun` |
|
|
61
|
+
| Risky | `risky - N risk(s) to review, no hard blockers` |
|
|
62
|
+
| Not implemented yet | `the scanner engine is not implemented yet (phase 1 scaffold only)` |
|
|
63
|
+
|
|
64
|
+
## Trademark hygiene
|
|
65
|
+
|
|
66
|
+
bunready is an independent community project. It is **not affiliated with,
|
|
67
|
+
endorsed by, or sponsored by** the Bun project or Oven. "Bun" and the Bun logo
|
|
68
|
+
belong to their respective owners and are used only for descriptive, nominative
|
|
69
|
+
purposes. bunready ships no Bun code and no Bun branding. Every public surface
|
|
70
|
+
(README, `--help`, OG image) carries the disclaimer.
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
<g>
|
|
4
|
+
<rect x="34" y="34" width="52" height="52" rx="15" fill="none" stroke="#E0A268" stroke-width="3"/>
|
|
5
|
+
<path d="M53 49 L68 60 L53 71" fill="none" stroke="#F2F1EC" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
6
|
+
<path d="M54 77 H68" fill="none" stroke="#F2F1EC" stroke-width="4.5" stroke-linecap="round"/>
|
|
7
|
+
</g>
|
|
8
|
+
<text x="108" y="76"
|
|
9
|
+
font-family="ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
|
|
10
|
+
font-size="42" font-weight="620" letter-spacing="-1" fill="#F2F1EC">bunready</text>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
|
4
|
+
<rect x="34" y="34" width="52" height="52" rx="15" stroke-width="3"/>
|
|
5
|
+
<path d="M53 49 L68 60 L53 71" stroke-width="4.5"/>
|
|
6
|
+
<path d="M54 77 H68" stroke-width="4.5"/>
|
|
7
|
+
</g>
|
|
8
|
+
<text x="108" y="76"
|
|
9
|
+
font-family="ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
|
|
10
|
+
font-size="42" font-weight="620" letter-spacing="-1" fill="currentColor">bunready</text>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
<g>
|
|
4
|
+
<rect x="34" y="34" width="52" height="52" rx="15" fill="none" stroke="#A85B2B" stroke-width="3"/>
|
|
5
|
+
<path d="M53 49 L68 60 L53 71" fill="none" stroke="#171A1F" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
6
|
+
<path d="M54 77 H68" fill="none" stroke="#171A1F" stroke-width="4.5" stroke-linecap="round"/>
|
|
7
|
+
</g>
|
|
8
|
+
<text x="108" y="76"
|
|
9
|
+
font-family="ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
|
|
10
|
+
font-size="42" font-weight="620" letter-spacing="-1" fill="#171A1F">bunready</text>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="bunready mark">
|
|
2
|
+
<title>bunready mark</title>
|
|
3
|
+
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
|
4
|
+
<rect x="6" y="6" width="52" height="52" rx="15" stroke-width="3"/>
|
|
5
|
+
<path d="M25 21 L40 32 L25 43" stroke-width="4.5"/>
|
|
6
|
+
<path d="M26 49 H40" stroke-width="4.5"/>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bunready",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"positioning": "Know what breaks before you move a Node/TS repo to Bun.",
|
|
5
|
+
"tagline": "One command. One honest verdict.",
|
|
6
|
+
"voice": ["calm", "precise", "non-alarmist", "specific"],
|
|
7
|
+
"color": {
|
|
8
|
+
"light": {
|
|
9
|
+
"bg": "#FBFAF7",
|
|
10
|
+
"surface": "#FFFFFF",
|
|
11
|
+
"border": "#E5E2DA",
|
|
12
|
+
"text": "#171A1F",
|
|
13
|
+
"muted": "#5C6470",
|
|
14
|
+
"accent": "#A85B2B",
|
|
15
|
+
"accentSoft": "#F3E4D7"
|
|
16
|
+
},
|
|
17
|
+
"dark": {
|
|
18
|
+
"bg": "#131519",
|
|
19
|
+
"surface": "#1A1D23",
|
|
20
|
+
"border": "#2A2F38",
|
|
21
|
+
"text": "#F2F1EC",
|
|
22
|
+
"muted": "#A2A9B4",
|
|
23
|
+
"accent": "#E0A268",
|
|
24
|
+
"accentSoft": "#3A2B1F"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"severity": {
|
|
28
|
+
"blocker": {
|
|
29
|
+
"light": "#B94A44",
|
|
30
|
+
"dark": "#E58079",
|
|
31
|
+
"label": "blocker",
|
|
32
|
+
"meaning": "Cannot run correctly on Bun until fixed. Fails CI."
|
|
33
|
+
},
|
|
34
|
+
"risk": {
|
|
35
|
+
"light": "#A9741A",
|
|
36
|
+
"dark": "#E0B463",
|
|
37
|
+
"label": "risk",
|
|
38
|
+
"meaning": "Real hazard needing a human judgement call. Does not fail CI."
|
|
39
|
+
},
|
|
40
|
+
"info": {
|
|
41
|
+
"light": "#5C6470",
|
|
42
|
+
"dark": "#A2A9B4",
|
|
43
|
+
"label": "info",
|
|
44
|
+
"meaning": "Context only. Does not fail CI."
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"terminal": {
|
|
48
|
+
"ansi256": {
|
|
49
|
+
"blocker": 203,
|
|
50
|
+
"risk": 179,
|
|
51
|
+
"info": 245,
|
|
52
|
+
"accent": 173,
|
|
53
|
+
"muted": 245,
|
|
54
|
+
"border": 240
|
|
55
|
+
},
|
|
56
|
+
"note": "Terminal output must stay legible on light and dark themes; never rely on colour alone - every severity also carries a word."
|
|
57
|
+
},
|
|
58
|
+
"spacing": [2, 4, 8, 12, 16, 24, 32, 48, 64],
|
|
59
|
+
"radii": { "none": 0, "sm": 4, "md": 8, "lg": 14, "pill": 999 },
|
|
60
|
+
"typeScale": {
|
|
61
|
+
"sans": [12, 14, 16, 20, 26, 34, 46, 60],
|
|
62
|
+
"mono": [12, 13, 14, 16, 20],
|
|
63
|
+
"lineHeight": { "tight": 1.15, "normal": 1.5, "loose": 1.7 }
|
|
64
|
+
},
|
|
65
|
+
"fonts": {
|
|
66
|
+
"sans": "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
|
67
|
+
"mono": "ui-monospace, SFMono-Regular, 'Cascadia Mono', Menlo, Consolas, 'Liberation Mono', monospace"
|
|
68
|
+
},
|
|
69
|
+
"clearSpace": {
|
|
70
|
+
"rule": "clear space on every side = 0.5 x the mark height",
|
|
71
|
+
"minMarkPx": 24,
|
|
72
|
+
"minLockupWidthPx": 120
|
|
73
|
+
}
|
|
74
|
+
}
|
package/docs/demo.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 30-second demo
|
|
2
|
+
|
|
3
|
+
The GIF this script produces is **not** committed here: it is recorded from the
|
|
4
|
+
real binary, never mocked up. Until the scanner engine lands (phases 2-3) there
|
|
5
|
+
is nothing honest to record, so this file is the shot list and the exact
|
|
6
|
+
commands.
|
|
7
|
+
|
|
8
|
+
## Preconditions
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
bun install
|
|
12
|
+
bun run src/cli/index.ts --version # prints: bunready 0.1.0
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Recording: 1200x700, 30fps, ~14s of content cut to a 30s budget, no fake
|
|
16
|
+
typing pauses. Terminal font 16px, dark theme with a light-theme counterpart
|
|
17
|
+
shot, `NO_COLOR` shot last.
|
|
18
|
+
|
|
19
|
+
## Shot list
|
|
20
|
+
|
|
21
|
+
| # | Duration | Terminal command | What the viewer must see |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| 1 | 3s | `bunready --help` | The positioning line, the exit-code table, the disclaimer. |
|
|
24
|
+
| 2 | 6s | `cd ~/code/some-node-service && bunready .` | The human table: findings grouped blocker / risk / info. |
|
|
25
|
+
| 3 | 4s | same, scrolled to the verdict | The single verdict line and the non-zero exit code. |
|
|
26
|
+
| 4 | 5s | `bunready . --json \| head -40` | The same findings as JSON, stable field names. |
|
|
27
|
+
| 5 | 4s | `bunready --run .` | The **first** real failure and its stack trace. |
|
|
28
|
+
| 6 | 3s | `NO_COLOR=1 bunready .` | Plain text, identical information, still a real verdict. |
|
|
29
|
+
| 7 | 5s | `bunready . ; echo "exit=$?"` | The exit code CI depends on. |
|
|
30
|
+
|
|
31
|
+
Total: 30s. No cuts inside a command; no invented output.
|
|
32
|
+
|
|
33
|
+
## Target repo for the recording
|
|
34
|
+
|
|
35
|
+
Pick a real repository with at least one native dependency and one blocked
|
|
36
|
+
lifecycle script, so every severity is genuinely present. Record its commit SHA
|
|
37
|
+
in the GIF description.
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mh-alikhani/bunready",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bun-readiness scanner: one command that shows what will break before you move a Node/TS repo to Bun.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bun",
|
|
7
|
+
"migration",
|
|
8
|
+
"compatibility",
|
|
9
|
+
"scanner",
|
|
10
|
+
"cli",
|
|
11
|
+
"node",
|
|
12
|
+
"typescript"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Mohammad Hosein Alikhani",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"private": false,
|
|
18
|
+
"bin": {
|
|
19
|
+
"bunready": "./src/cli/index.ts"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src",
|
|
23
|
+
"docs",
|
|
24
|
+
"action.yml",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"CHANGELOG.md"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"bun": ">=1.4.0"
|
|
34
|
+
},
|
|
35
|
+
"trustedDependencies": [
|
|
36
|
+
"simple-git-hooks"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"bunready": "bun run src/cli/index.ts",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"lint": "biome check .",
|
|
42
|
+
"lint:fix": "biome check --write .",
|
|
43
|
+
"format": "biome format --write .",
|
|
44
|
+
"test": "bun test",
|
|
45
|
+
"build": "bun build ./src/cli/index.ts --target=bun --outdir=dist",
|
|
46
|
+
"check": "bun run typecheck && bun run lint && bun run test",
|
|
47
|
+
"og": "bun run scripts/generate-og-image.ts",
|
|
48
|
+
"prepare": "simple-git-hooks"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@biomejs/biome": "^2",
|
|
52
|
+
"@commitlint/cli": "^21",
|
|
53
|
+
"@commitlint/config-conventional": "^21",
|
|
54
|
+
"@resvg/resvg-js": "^2",
|
|
55
|
+
"@types/bun": "^1",
|
|
56
|
+
"simple-git-hooks": "^2",
|
|
57
|
+
"typescript": "^7"
|
|
58
|
+
},
|
|
59
|
+
"simple-git-hooks": {
|
|
60
|
+
"pre-commit": "bunx biome check --staged --no-errors-on-unmatched && bunx tsc --noEmit",
|
|
61
|
+
"commit-msg": "bunx commitlint --edit \"$1\""
|
|
62
|
+
},
|
|
63
|
+
"repository": {
|
|
64
|
+
"type": "git",
|
|
65
|
+
"url": "git+https://github.com/MHAlikhani/bunready.git"
|
|
66
|
+
},
|
|
67
|
+
"homepage": "https://github.com/MHAlikhani/bunready#readme",
|
|
68
|
+
"bugs": {
|
|
69
|
+
"url": "https://github.com/MHAlikhani/bunready/issues"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/cli/args.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { defineError, err, ok, type Result } from "../core/errors";
|
|
2
|
+
|
|
3
|
+
/** Everything the CLI understands. */
|
|
4
|
+
export interface CliOptions {
|
|
5
|
+
/** Repository to scan; `.` unless the user passed a path. */
|
|
6
|
+
readonly target: string;
|
|
7
|
+
readonly help: boolean;
|
|
8
|
+
readonly version: boolean;
|
|
9
|
+
readonly json: boolean;
|
|
10
|
+
readonly sarif: boolean;
|
|
11
|
+
readonly run: boolean;
|
|
12
|
+
readonly runScript: string | undefined;
|
|
13
|
+
readonly config: string | undefined;
|
|
14
|
+
readonly scope: string | undefined;
|
|
15
|
+
readonly baseline: string | undefined;
|
|
16
|
+
readonly writeBaseline: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_TARGET = ".";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse argv (already stripped of `node`/`bun` and the script path).
|
|
23
|
+
*
|
|
24
|
+
* Usage problems are returned, not thrown: the caller decides the exit code and
|
|
25
|
+
* the message. `--` ends option parsing so a directory named `--json` stays
|
|
26
|
+
* addressable.
|
|
27
|
+
*/
|
|
28
|
+
export function parseArgs(argv: readonly string[]): Result<CliOptions> {
|
|
29
|
+
let target: string | undefined;
|
|
30
|
+
let help = false;
|
|
31
|
+
let version = false;
|
|
32
|
+
let json = false;
|
|
33
|
+
let sarif = false;
|
|
34
|
+
let run = false;
|
|
35
|
+
let runScript: string | undefined;
|
|
36
|
+
let config: string | undefined;
|
|
37
|
+
let scope: string | undefined;
|
|
38
|
+
let baseline: string | undefined;
|
|
39
|
+
let writeBaseline: string | undefined;
|
|
40
|
+
let positionalOnly = false;
|
|
41
|
+
|
|
42
|
+
const valueFor = (flag: string, index: number): Result<string> => {
|
|
43
|
+
const value = argv[index + 1];
|
|
44
|
+
if (value === undefined || value === "") {
|
|
45
|
+
return err(
|
|
46
|
+
defineError("E_USAGE", `${flag} needs a value`, {
|
|
47
|
+
hint: `write it as ${flag} <value>.`,
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return ok(value);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
55
|
+
const arg = argv[index] ?? "";
|
|
56
|
+
|
|
57
|
+
if (positionalOnly) {
|
|
58
|
+
if (target !== undefined) {
|
|
59
|
+
return err(
|
|
60
|
+
defineError("E_USAGE", `unexpected extra argument "${arg}"`, {
|
|
61
|
+
hint: "bunready scans one repository at a time.",
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
target = arg;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
switch (arg) {
|
|
70
|
+
case "--":
|
|
71
|
+
positionalOnly = true;
|
|
72
|
+
break;
|
|
73
|
+
case "-h":
|
|
74
|
+
case "--help":
|
|
75
|
+
help = true;
|
|
76
|
+
break;
|
|
77
|
+
case "-v":
|
|
78
|
+
case "--version":
|
|
79
|
+
version = true;
|
|
80
|
+
break;
|
|
81
|
+
case "--json":
|
|
82
|
+
json = true;
|
|
83
|
+
break;
|
|
84
|
+
case "--sarif":
|
|
85
|
+
sarif = true;
|
|
86
|
+
break;
|
|
87
|
+
case "--run":
|
|
88
|
+
run = true;
|
|
89
|
+
break;
|
|
90
|
+
case "--run-script": {
|
|
91
|
+
const value = valueFor(arg, index);
|
|
92
|
+
if (!value.ok) {
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
runScript = value.value;
|
|
96
|
+
run = true;
|
|
97
|
+
index += 1;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case "--config": {
|
|
101
|
+
const value = valueFor(arg, index);
|
|
102
|
+
if (!value.ok) {
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
config = value.value;
|
|
106
|
+
index += 1;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case "--scope": {
|
|
110
|
+
const value = valueFor(arg, index);
|
|
111
|
+
if (!value.ok) {
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
scope = value.value;
|
|
115
|
+
index += 1;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "--baseline": {
|
|
119
|
+
const value = valueFor(arg, index);
|
|
120
|
+
if (!value.ok) {
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
baseline = value.value;
|
|
124
|
+
index += 1;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case "--write-baseline": {
|
|
128
|
+
const value = valueFor(arg, index);
|
|
129
|
+
if (!value.ok) {
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
writeBaseline = value.value;
|
|
133
|
+
index += 1;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
default:
|
|
137
|
+
if (arg.startsWith("-") && arg !== "-") {
|
|
138
|
+
return err(
|
|
139
|
+
defineError("E_USAGE", `unknown option "${arg}"`, {
|
|
140
|
+
hint: "run `bunready --help` to see the supported options.",
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (target !== undefined) {
|
|
145
|
+
return err(
|
|
146
|
+
defineError("E_USAGE", `unexpected extra argument "${arg}"`, {
|
|
147
|
+
hint: "bunready scans one repository at a time.",
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
target = arg;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (json && sarif) {
|
|
157
|
+
return err(
|
|
158
|
+
defineError("E_USAGE", "--json and --sarif both write a machine-readable report to stdout", {
|
|
159
|
+
hint: "choose one of them.",
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return ok({
|
|
165
|
+
target: target ?? DEFAULT_TARGET,
|
|
166
|
+
help,
|
|
167
|
+
version,
|
|
168
|
+
json,
|
|
169
|
+
sarif,
|
|
170
|
+
run,
|
|
171
|
+
runScript,
|
|
172
|
+
config,
|
|
173
|
+
scope,
|
|
174
|
+
baseline,
|
|
175
|
+
writeBaseline,
|
|
176
|
+
});
|
|
177
|
+
}
|
package/src/cli/copy.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-facing copy. This file is the single source of truth for what bunready
|
|
3
|
+
* says, so the brand guidelines and the binary cannot drift apart.
|
|
4
|
+
*
|
|
5
|
+
* Voice: calm, precise, non-alarmist. State the fact, then the next step.
|
|
6
|
+
*/
|
|
7
|
+
import { TOOL_NAME } from "../core/version";
|
|
8
|
+
import type { Verdict } from "../report/types";
|
|
9
|
+
import type { Severity } from "../rules/severity";
|
|
10
|
+
|
|
11
|
+
export const TOOL = TOOL_NAME;
|
|
12
|
+
|
|
13
|
+
export const POSITIONING = "Know what breaks before you move a Node/TS repo to Bun.";
|
|
14
|
+
|
|
15
|
+
export const TAGLINE = "One command. One honest verdict.";
|
|
16
|
+
|
|
17
|
+
/** `--run` executes the target's code, so the help text has to say so plainly. */
|
|
18
|
+
export const RUN_WARNING =
|
|
19
|
+
"--run executes the target's code in a temporary copy; nothing runs in place and every command is timed";
|
|
20
|
+
|
|
21
|
+
export function helpText(version: string): string {
|
|
22
|
+
return [
|
|
23
|
+
`${TOOL} ${version}`,
|
|
24
|
+
POSITIONING,
|
|
25
|
+
"",
|
|
26
|
+
"USAGE",
|
|
27
|
+
` ${TOOL} [path] [options]`,
|
|
28
|
+
"",
|
|
29
|
+
"ARGUMENTS",
|
|
30
|
+
" path Repository to scan. Defaults to the current directory.",
|
|
31
|
+
"",
|
|
32
|
+
"OPTIONS",
|
|
33
|
+
" -h, --help Print this help and exit.",
|
|
34
|
+
" -v, --version Print the version and exit.",
|
|
35
|
+
" --json Emit a machine-readable report on stdout.",
|
|
36
|
+
" --sarif Emit SARIF 2.1.0 on stdout, for code-scanning upload.",
|
|
37
|
+
" --run Copy the repository to a temporary directory, install it and",
|
|
38
|
+
" run its start (or test) script under Bun, reporting the first",
|
|
39
|
+
" real failure. Executes code; off by default.",
|
|
40
|
+
"",
|
|
41
|
+
" --config Path to a bunready.config.json. Defaults to the file in the",
|
|
42
|
+
" scanned repository, if any.",
|
|
43
|
+
" --scope In a workspace, scan only packages matching this string.",
|
|
44
|
+
" --baseline Compare against a baseline and fail only on new findings.",
|
|
45
|
+
" --write-baseline Record the current findings as a baseline.",
|
|
46
|
+
"",
|
|
47
|
+
"EXIT CODES",
|
|
48
|
+
" 0 No blockers found.",
|
|
49
|
+
" 1 Blockers found.",
|
|
50
|
+
" 2 Usage error, or the scan could not complete.",
|
|
51
|
+
"",
|
|
52
|
+
"ENVIRONMENT",
|
|
53
|
+
" NO_COLOR Disable ANSI colour (presence is enough).",
|
|
54
|
+
"",
|
|
55
|
+
"Compatibility claims come only from public Bun documentation and issue",
|
|
56
|
+
"tracker entries, each shipped with a source link. bunready never invents",
|
|
57
|
+
"compatibility facts. Not affiliated with the Bun project or Oven.",
|
|
58
|
+
"",
|
|
59
|
+
"CONFIGURATION",
|
|
60
|
+
" bunready.config.json in the scanned repository can ignore rule ids or",
|
|
61
|
+
" packages, allowlist native addons, exclude paths and raise or lower",
|
|
62
|
+
" failOn. See docs/CONFIGURATION.md.",
|
|
63
|
+
].join("\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The single line a human reads first. Never alarmist, never vague. */
|
|
67
|
+
export function verdictLine(verdict: Verdict, counts: Readonly<Record<Severity, number>>): string {
|
|
68
|
+
switch (verdict) {
|
|
69
|
+
case "blocked":
|
|
70
|
+
return `blocked - ${counts.blocker} blocker(s) must be fixed before this repo runs on Bun`;
|
|
71
|
+
case "risky":
|
|
72
|
+
return `risky - ${counts.risk} risk(s) to review, no hard blockers`;
|
|
73
|
+
case "ready":
|
|
74
|
+
return "ready - no Bun compatibility blockers found";
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/cli/index.ts
ADDED
package/src/cli/io.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Terminal seam: `run()` writes through this, never straight to `process`. */
|
|
2
|
+
export interface Io {
|
|
3
|
+
readonly out: (line: string) => void;
|
|
4
|
+
readonly err: (line: string) => void;
|
|
5
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
6
|
+
readonly isTty: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function systemIo(): Io {
|
|
10
|
+
return {
|
|
11
|
+
out: (line) => {
|
|
12
|
+
process.stdout.write(`${line}\n`);
|
|
13
|
+
},
|
|
14
|
+
err: (line) => {
|
|
15
|
+
process.stderr.write(`${line}\n`);
|
|
16
|
+
},
|
|
17
|
+
env: process.env,
|
|
18
|
+
isTty: process.stdout.isTTY === true,
|
|
19
|
+
};
|
|
20
|
+
}
|