@mh-alikhani/bunready 0.2.0 → 0.3.1
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 +41 -1
- package/README.md +1 -1
- package/docs/brand/logo-card.svg +20 -0
- package/package.json +7 -1
- package/src/index.ts +95 -0
- package/src/scanner/semver.ts +6 -6
- package/src/scanner/workspaces.ts +27 -5
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,43 @@ 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.1] - 2026-09-19
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Three polynomial-time regular expressions, reported by CodeQL as
|
|
17
|
+
`js/polynomial-redos`. bunready parses files it does not control - a
|
|
18
|
+
`pnpm-workspace.yaml` from a repository being scanned, a semver range in its
|
|
19
|
+
`engines` - so a pathological string could have made a scan take quadratic
|
|
20
|
+
time. The pnpm list item and the semver operator split are now parsed
|
|
21
|
+
directly, and trailing-slash stripping is a linear loop instead of `/+$`.
|
|
22
|
+
Behaviour is unchanged; `tests/workspaces.test.ts` and `tests/semver.test.ts`
|
|
23
|
+
cover the affected paths.
|
|
24
|
+
|
|
25
|
+
## [0.3.0] - 2026-09-18
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- A programmatic entry point (`src/index.ts`, wired to `main` and `exports`), so
|
|
30
|
+
the scanner can be driven from a script or a test instead of a child process -
|
|
31
|
+
and so npm can resolve the package instead of reporting that it cannot guess an
|
|
32
|
+
entry point.
|
|
33
|
+
- `docs/brand/logo-card.svg`: a lockup that carries its own surface, used as the
|
|
34
|
+
README fallback where `<picture>` is stripped (npm). An SVG loaded through
|
|
35
|
+
`<img>` resolves `prefers-color-scheme` against the reader's operating system
|
|
36
|
+
rather than against the page, so a transparent lockup can render
|
|
37
|
+
light-on-white.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- The README logo is legible on npm in both colour schemes.
|
|
42
|
+
- `tests/args-flags.test.ts` compared an expression with itself, which CodeQL
|
|
43
|
+
reported as a redundant operation (`js/redundant-operation`).
|
|
44
|
+
|
|
45
|
+
### Docs
|
|
46
|
+
|
|
47
|
+
- Changelog link references for 0.2.0 and 0.3.0 were missing.
|
|
48
|
+
|
|
12
49
|
## [0.2.0] - 2026-09-16
|
|
13
50
|
|
|
14
51
|
### Added
|
|
@@ -164,5 +201,8 @@ Nothing yet.
|
|
|
164
201
|
and that claim needs a primary source. Until then the rule reports what the
|
|
165
202
|
repository imports and cites the compatibility table.
|
|
166
203
|
|
|
167
|
-
[Unreleased]: https://github.com/MHAlikhani/bunready/compare/v0.1
|
|
204
|
+
[Unreleased]: https://github.com/MHAlikhani/bunready/compare/v0.3.1...HEAD
|
|
205
|
+
[0.3.1]: https://github.com/MHAlikhani/bunready/releases/tag/v0.3.1
|
|
206
|
+
[0.3.0]: https://github.com/MHAlikhani/bunready/releases/tag/v0.3.0
|
|
207
|
+
[0.2.0]: https://github.com/MHAlikhani/bunready/releases/tag/v0.2.0
|
|
168
208
|
[0.1.0]: https://github.com/MHAlikhani/bunready/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<picture>
|
|
4
4
|
<source media="(prefers-color-scheme: dark)" srcset="docs/brand/logo-dark.svg">
|
|
5
5
|
<source media="(prefers-color-scheme: light)" srcset="docs/brand/logo.svg">
|
|
6
|
-
<img src="docs/brand/logo-
|
|
6
|
+
<img src="docs/brand/logo-card.svg" alt="bunready" width="360">
|
|
7
7
|
</picture>
|
|
8
8
|
|
|
9
9
|
**One command that tells you what will break before you move a Node/TS repo to Bun — and gives you one clear verdict.**
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
The lockup for hosts that give us no background guarantee.
|
|
5
|
+
npm renders a README without <picture>, and an SVG loaded through <img>
|
|
6
|
+
resolves prefers-color-scheme against the reader's operating system rather
|
|
7
|
+
than the page - so a transparent lockup can end up light-on-white. This one
|
|
8
|
+
carries its own surface, which is why it is the fallback rather than the
|
|
9
|
+
default.
|
|
10
|
+
-->
|
|
11
|
+
<rect x="0.5" y="0.5" width="339" height="119" rx="18" fill="#171a1f" stroke="#3a3f4a"/>
|
|
12
|
+
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
13
|
+
<rect x="34" y="34" width="52" height="52" rx="15" stroke="#e0a268" stroke-width="3"/>
|
|
14
|
+
<path d="M53 49 L68 60 L53 71" stroke="#fbfaf7" stroke-width="4.5"/>
|
|
15
|
+
<path d="M54 77 H68" stroke="#fbfaf7" stroke-width="4.5"/>
|
|
16
|
+
</g>
|
|
17
|
+
<text x="108" y="76"
|
|
18
|
+
font-family="ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
|
|
19
|
+
font-size="42" font-weight="620" letter-spacing="-1" fill="#f2f1ec">bunready</text>
|
|
20
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mh-alikhani/bunready",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
@@ -27,6 +27,12 @@
|
|
|
27
27
|
"author": "Mohammad Hosein Alikhani",
|
|
28
28
|
"type": "module",
|
|
29
29
|
"private": false,
|
|
30
|
+
"main": "./src/index.ts",
|
|
31
|
+
"types": "./src/index.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": "./src/index.ts",
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
30
36
|
"bin": {
|
|
31
37
|
"bunready": "./src/cli/index.ts"
|
|
32
38
|
},
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Programmatic entry point.
|
|
3
|
+
*
|
|
4
|
+
* bunready is a CLI first. This module exists so the same checks can be driven
|
|
5
|
+
* from a script or a test without shelling out to a process, and so the package
|
|
6
|
+
* has a real entry point instead of only a `bin` - npm cannot resolve a package
|
|
7
|
+
* that declares neither `main` nor `exports`.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here is re-exported by accident; if a name is in this file, it is part
|
|
10
|
+
* of the package's public surface.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { type CliOptions, parseArgs } from "./cli/args";
|
|
14
|
+
export { run } from "./cli/run";
|
|
15
|
+
export { createTheme, type Theme } from "./cli/theme";
|
|
16
|
+
export {
|
|
17
|
+
applyBaseline,
|
|
18
|
+
type Baseline,
|
|
19
|
+
type BaselineSummary,
|
|
20
|
+
fingerprint,
|
|
21
|
+
parseBaseline,
|
|
22
|
+
serializeBaseline,
|
|
23
|
+
} from "./config/baseline";
|
|
24
|
+
export {
|
|
25
|
+
type BunreadyConfig,
|
|
26
|
+
CONFIG_FILENAME,
|
|
27
|
+
DEFAULT_CONFIG,
|
|
28
|
+
parseConfig,
|
|
29
|
+
type RunConfig,
|
|
30
|
+
} from "./config/config";
|
|
31
|
+
export {
|
|
32
|
+
type BunreadyError,
|
|
33
|
+
defineError,
|
|
34
|
+
type ErrorCode,
|
|
35
|
+
err,
|
|
36
|
+
formatError,
|
|
37
|
+
isErr,
|
|
38
|
+
isOk,
|
|
39
|
+
ok,
|
|
40
|
+
type Result,
|
|
41
|
+
} from "./core/errors";
|
|
42
|
+
export { type DirectoryEntry, type FileSystem, nodeFileSystem, type ReadOutcome } from "./core/fs";
|
|
43
|
+
export { TOOL_NAME, TOOL_VERSION } from "./core/version";
|
|
44
|
+
export { renderHumanReport } from "./report/human";
|
|
45
|
+
export { renderJsonReport } from "./report/json";
|
|
46
|
+
export { renderSarifReport } from "./report/sarif";
|
|
47
|
+
export {
|
|
48
|
+
type Finding,
|
|
49
|
+
type RunSummary,
|
|
50
|
+
SCHEMA_VERSION,
|
|
51
|
+
type ScannedTarget,
|
|
52
|
+
type ScanReport,
|
|
53
|
+
type ScanStats,
|
|
54
|
+
sortFindings,
|
|
55
|
+
type Verdict,
|
|
56
|
+
verdictFor,
|
|
57
|
+
} from "./report/types";
|
|
58
|
+
export { installFindings } from "./rules/install";
|
|
59
|
+
export { runFindings } from "./rules/run";
|
|
60
|
+
export { runtimeFindings } from "./rules/runtime";
|
|
61
|
+
export {
|
|
62
|
+
type BuiltinUsage,
|
|
63
|
+
collectNodeBuiltins,
|
|
64
|
+
readRuntimeDataset,
|
|
65
|
+
} from "./rules/runtime/builtins";
|
|
66
|
+
export {
|
|
67
|
+
countBySeverity,
|
|
68
|
+
exitCodeForFindings,
|
|
69
|
+
exitCodeForSeverities,
|
|
70
|
+
SEVERITIES,
|
|
71
|
+
type Severity,
|
|
72
|
+
} from "./rules/severity";
|
|
73
|
+
export {
|
|
74
|
+
executeProject,
|
|
75
|
+
firstFailure,
|
|
76
|
+
type RunEnvironment,
|
|
77
|
+
type RunOptions,
|
|
78
|
+
type RunOutcome,
|
|
79
|
+
systemRunEnvironment,
|
|
80
|
+
} from "./scanner/execute";
|
|
81
|
+
export { buildGraph, type DependencyGraph, knownPackageNames } from "./scanner/graph";
|
|
82
|
+
export {
|
|
83
|
+
LOCKFILE_FILENAMES,
|
|
84
|
+
type LockedPackage,
|
|
85
|
+
type ParsedLockfile,
|
|
86
|
+
parseLockfile,
|
|
87
|
+
} from "./scanner/lockfile";
|
|
88
|
+
export { type Manifest, parseManifest } from "./scanner/manifest";
|
|
89
|
+
export { detectRuntime, type ScanOptions, scanTarget } from "./scanner/scan";
|
|
90
|
+
export { classifySpecifier, extractImports, type SourceScan, scanSources } from "./scanner/sources";
|
|
91
|
+
export {
|
|
92
|
+
findWorkspacePackages,
|
|
93
|
+
type WorkspacePackage,
|
|
94
|
+
workspacePatterns,
|
|
95
|
+
} from "./scanner/workspaces";
|
package/src/scanner/semver.ts
CHANGED
|
@@ -92,12 +92,12 @@ function comparatorsForToken(token: string): Comparator[] | undefined {
|
|
|
92
92
|
return [];
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const operator = operatorMatch[1];
|
|
100
|
-
const rest = operatorMatch[
|
|
95
|
+
// Anchored-prefix parse rather than a trailing \s*(.*)$, which backtracks
|
|
96
|
+
// quadratically on a long run of spaces - and the range comes from a
|
|
97
|
+
// repository bunready does not control.
|
|
98
|
+
const operatorMatch = /^(>=|<=|>|<|=|\^|~)?/.exec(trimmed);
|
|
99
|
+
const operator = operatorMatch?.[1];
|
|
100
|
+
const rest = trimmed.slice(operatorMatch?.[0].length ?? 0).trim();
|
|
101
101
|
if (rest === "" || rest === "*" || rest === "x") {
|
|
102
102
|
return [];
|
|
103
103
|
}
|
|
@@ -54,9 +54,22 @@ export function patternsFromPnpmWorkspace(text: string): string[] {
|
|
|
54
54
|
if (!inPackages) {
|
|
55
55
|
continue;
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
// Walked rather than matched: a pattern mixing a dash run, a space run and a
|
|
58
|
+
// catch-all is ambiguous, and CodeQL is right that it backtracks on a string
|
|
59
|
+
// of dashes and spaces. The value is trimmed here instead.
|
|
60
|
+
let cursor = 0;
|
|
61
|
+
while (cursor < trimmed.length && trimmed[cursor] === "-") {
|
|
62
|
+
cursor += 1;
|
|
63
|
+
}
|
|
64
|
+
const value =
|
|
65
|
+
cursor === 0
|
|
66
|
+
? ""
|
|
67
|
+
: trimmed
|
|
68
|
+
.slice(cursor)
|
|
69
|
+
.trim()
|
|
70
|
+
.replace(/^['"]|['"]$/g, "");
|
|
71
|
+
if (value !== "") {
|
|
72
|
+
patterns.push(value);
|
|
60
73
|
}
|
|
61
74
|
}
|
|
62
75
|
|
|
@@ -123,6 +136,15 @@ async function expandGlob(root: string, segments: string[], fs: FileSystem): Pro
|
|
|
123
136
|
return prefixes.filter((prefix) => prefix !== "" && prefix !== ".");
|
|
124
137
|
}
|
|
125
138
|
|
|
139
|
+
/** Linear replacement for `/+$`, which backtracks on a run of slashes. */
|
|
140
|
+
function stripTrailingSlashes(value: string): string {
|
|
141
|
+
let end = value.length;
|
|
142
|
+
while (end > 0 && value[end - 1] === "/") {
|
|
143
|
+
end -= 1;
|
|
144
|
+
}
|
|
145
|
+
return value.slice(0, end);
|
|
146
|
+
}
|
|
147
|
+
|
|
126
148
|
export async function findWorkspacePackages(
|
|
127
149
|
root: string,
|
|
128
150
|
patterns: readonly string[],
|
|
@@ -131,7 +153,7 @@ export async function findWorkspacePackages(
|
|
|
131
153
|
const found = new Map<string, WorkspacePackage>();
|
|
132
154
|
|
|
133
155
|
for (const pattern of patterns) {
|
|
134
|
-
const cleaned = pattern.replace(/\\/g, "/")
|
|
156
|
+
const cleaned = stripTrailingSlashes(pattern.replace(/\\/g, "/")).replace(/^\.\//, "");
|
|
135
157
|
if (cleaned === "" || cleaned === ".") {
|
|
136
158
|
continue;
|
|
137
159
|
}
|
|
@@ -165,6 +187,6 @@ export async function readPnpmWorkspace(root: string, fs: FileSystem): Promise<s
|
|
|
165
187
|
|
|
166
188
|
/** Keep only the packages matching a scope string, by relative path or name. */
|
|
167
189
|
export function scopeMatches(relative: string, scope: string): boolean {
|
|
168
|
-
const needle = scope.replace(/\\/g, "/").replace(/^\.\//, "")
|
|
190
|
+
const needle = stripTrailingSlashes(scope.replace(/\\/g, "/")).replace(/^\.\//, "");
|
|
169
191
|
return relative === needle || relative.includes(needle);
|
|
170
192
|
}
|