bun-ready 0.1.0 → 0.2.3
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/README.md +139 -0
- package/dist/cli.js +1024 -145
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, {
|
|
5
|
+
get: all[name],
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
set: (newValue) => all[name] = () => newValue
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
11
12
|
|
|
12
13
|
// src/spawn.ts
|
|
13
14
|
import { spawn } from "node:child_process";
|
|
@@ -20,8 +21,7 @@ var runNodeSpawn = async (cmd, args, cwd) => {
|
|
|
20
21
|
child.stderr.on("data", (d) => err += String(d));
|
|
21
22
|
child.on("close", (code) => resolve({ code: code ?? 1, stdout: out, stderr: err }));
|
|
22
23
|
});
|
|
23
|
-
}
|
|
24
|
-
var runBunSpawn = async (cmd, args, cwd) => {
|
|
24
|
+
}, runBunSpawn = async (cmd, args, cwd) => {
|
|
25
25
|
const BunAny = globalThis;
|
|
26
26
|
const bun = BunAny.Bun;
|
|
27
27
|
if (!bun)
|
|
@@ -48,10 +48,53 @@ var runBunSpawn = async (cmd, args, cwd) => {
|
|
|
48
48
|
};
|
|
49
49
|
const [stdout, stderr, code] = await Promise.all([readAll(proc.stdout), readAll(proc.stderr), proc.exited]);
|
|
50
50
|
return { code, stdout, stderr };
|
|
51
|
-
}
|
|
52
|
-
var exec = async (cmd, args, cwd) => {
|
|
51
|
+
}, exec = async (cmd, args, cwd) => {
|
|
53
52
|
return await runBunSpawn(cmd, args, cwd);
|
|
54
53
|
};
|
|
54
|
+
var init_spawn = () => {};
|
|
55
|
+
|
|
56
|
+
// src/bun_check.ts
|
|
57
|
+
var exports_bun_check = {};
|
|
58
|
+
__export(exports_bun_check, {
|
|
59
|
+
checkBunAvailable: () => checkBunAvailable
|
|
60
|
+
});
|
|
61
|
+
async function checkBunAvailable() {
|
|
62
|
+
try {
|
|
63
|
+
const res = await exec("bun", ["--version"], process.cwd());
|
|
64
|
+
if (res.code === 0 && res.stdout.includes("bun")) {
|
|
65
|
+
return { available: true };
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
available: false,
|
|
69
|
+
error: `Bun command returned unexpected output (exit ${res.code})`
|
|
70
|
+
};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
73
|
+
if (msg.includes("ENOENT") || msg.includes("spawn bun ENOENT")) {
|
|
74
|
+
return {
|
|
75
|
+
available: false,
|
|
76
|
+
error: "Bun is not installed. Please install Bun from https://bun.sh or use --no-install and --no-test flags."
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
available: false,
|
|
81
|
+
error: `Failed to check Bun availability: ${msg}`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
var init_bun_check = __esm(() => {
|
|
86
|
+
init_spawn();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// src/cli.ts
|
|
90
|
+
import { promises as fs3 } from "node:fs";
|
|
91
|
+
import path5 from "node:path";
|
|
92
|
+
|
|
93
|
+
// src/analyze.ts
|
|
94
|
+
init_spawn();
|
|
95
|
+
import path4 from "node:path";
|
|
96
|
+
import os from "node:os";
|
|
97
|
+
import { promises as fs2 } from "node:fs";
|
|
55
98
|
|
|
56
99
|
// src/util.ts
|
|
57
100
|
import { promises as fs } from "node:fs";
|
|
@@ -81,7 +124,7 @@ var truncateLines = (lines, max) => {
|
|
|
81
124
|
};
|
|
82
125
|
|
|
83
126
|
// src/heuristics.ts
|
|
84
|
-
var
|
|
127
|
+
var NATIVE_SUSPECTS_V2 = [
|
|
85
128
|
"node-gyp",
|
|
86
129
|
"node-pre-gyp",
|
|
87
130
|
"prebuild-install",
|
|
@@ -97,7 +140,112 @@ var NATIVE_SUSPECTS = [
|
|
|
97
140
|
"argon2",
|
|
98
141
|
"bufferutil",
|
|
99
142
|
"utf-8-validate",
|
|
100
|
-
"fsevents"
|
|
143
|
+
"fsevents",
|
|
144
|
+
"grpc",
|
|
145
|
+
"@grpc/grpc-js",
|
|
146
|
+
"grpc-js",
|
|
147
|
+
"bcryptjs",
|
|
148
|
+
"bcrypt",
|
|
149
|
+
"sodium",
|
|
150
|
+
"libsodium",
|
|
151
|
+
"leveldb",
|
|
152
|
+
"level",
|
|
153
|
+
"rocksdb",
|
|
154
|
+
"mysql2",
|
|
155
|
+
"pg",
|
|
156
|
+
"oracledb",
|
|
157
|
+
"nodegit",
|
|
158
|
+
"ffi-napi",
|
|
159
|
+
"node-ffi",
|
|
160
|
+
"ref-napi",
|
|
161
|
+
"skia-canvas",
|
|
162
|
+
"jimp",
|
|
163
|
+
"pdfkit",
|
|
164
|
+
"sharp",
|
|
165
|
+
"pixelmatch",
|
|
166
|
+
"cheerio",
|
|
167
|
+
"node-wav",
|
|
168
|
+
"lamejs",
|
|
169
|
+
"flac-bindings",
|
|
170
|
+
"opus-recorder",
|
|
171
|
+
"silk-wasm",
|
|
172
|
+
"zeromq",
|
|
173
|
+
"zeromq.js",
|
|
174
|
+
"mongodb",
|
|
175
|
+
"redis",
|
|
176
|
+
"ioredis",
|
|
177
|
+
"elasticsearch",
|
|
178
|
+
"snappy",
|
|
179
|
+
"snappyjs",
|
|
180
|
+
"iltorb",
|
|
181
|
+
"brotli",
|
|
182
|
+
"node-sha3",
|
|
183
|
+
"ursa",
|
|
184
|
+
"node-forge",
|
|
185
|
+
"jsonwebtoken",
|
|
186
|
+
"node-cron",
|
|
187
|
+
"bull",
|
|
188
|
+
"bullmq"
|
|
189
|
+
];
|
|
190
|
+
var DEV_TOOL_SUSPECTS = [
|
|
191
|
+
"jest",
|
|
192
|
+
"vitest",
|
|
193
|
+
"mocha",
|
|
194
|
+
"chai",
|
|
195
|
+
"ava",
|
|
196
|
+
"tap",
|
|
197
|
+
"jasmine",
|
|
198
|
+
"karma",
|
|
199
|
+
"cypress",
|
|
200
|
+
"playwright",
|
|
201
|
+
"puppeteer",
|
|
202
|
+
"selenium-webdriver",
|
|
203
|
+
"webdriverio",
|
|
204
|
+
"nightwatch",
|
|
205
|
+
"testcafe",
|
|
206
|
+
"protractor"
|
|
207
|
+
];
|
|
208
|
+
var RUNTIME_TOOL_SUSPECTS = [
|
|
209
|
+
"ts-node",
|
|
210
|
+
"tsx",
|
|
211
|
+
"ts-node-dev",
|
|
212
|
+
"nodemon",
|
|
213
|
+
"babel",
|
|
214
|
+
"@babel/core",
|
|
215
|
+
"@babel/node",
|
|
216
|
+
"babel-cli",
|
|
217
|
+
"babel-register",
|
|
218
|
+
"babel-preset-env",
|
|
219
|
+
"webpack",
|
|
220
|
+
"webpack-cli",
|
|
221
|
+
"rollup",
|
|
222
|
+
"@rollup/plugin",
|
|
223
|
+
"esbuild",
|
|
224
|
+
"vite",
|
|
225
|
+
"@vitejs/plugin",
|
|
226
|
+
"swc",
|
|
227
|
+
"@swc/core",
|
|
228
|
+
"@swc/register",
|
|
229
|
+
"turbopack",
|
|
230
|
+
"snowpack",
|
|
231
|
+
"parcel",
|
|
232
|
+
"browserify"
|
|
233
|
+
];
|
|
234
|
+
var PM_SPECIFIC_COMMANDS = [
|
|
235
|
+
"npm ci",
|
|
236
|
+
"npm run ci",
|
|
237
|
+
"pnpm -r",
|
|
238
|
+
"pnpm recursive",
|
|
239
|
+
"pnpm workspaces",
|
|
240
|
+
"yarn workspaces",
|
|
241
|
+
"yarn workspace",
|
|
242
|
+
"yarn -w",
|
|
243
|
+
"lerna run",
|
|
244
|
+
"nx run",
|
|
245
|
+
"npx lerna",
|
|
246
|
+
"npx nx",
|
|
247
|
+
"turbo run",
|
|
248
|
+
"rushx"
|
|
101
249
|
];
|
|
102
250
|
var includesAny = (s, needles) => {
|
|
103
251
|
const lower = s.toLowerCase();
|
|
@@ -144,31 +292,6 @@ var detectScriptRisks = (repo) => {
|
|
|
144
292
|
}
|
|
145
293
|
return findings;
|
|
146
294
|
};
|
|
147
|
-
var detectNativeAddonRisk = (repo) => {
|
|
148
|
-
const allDeps = {
|
|
149
|
-
...repo.dependencies,
|
|
150
|
-
...repo.devDependencies,
|
|
151
|
-
...repo.optionalDependencies
|
|
152
|
-
};
|
|
153
|
-
const names = Object.keys(allDeps);
|
|
154
|
-
const suspects = stableSort(names.filter((n) => NATIVE_SUSPECTS.includes(n) || includesAny(n, ["napi", "node-gyp", "prebuild", "ffi"])), (x) => x);
|
|
155
|
-
if (suspects.length === 0)
|
|
156
|
-
return [];
|
|
157
|
-
const hardRed = suspects.some((n) => n === "node-gyp" || n === "node-sass");
|
|
158
|
-
const severity = hardRed ? "red" : "yellow";
|
|
159
|
-
return [
|
|
160
|
-
{
|
|
161
|
-
id: "deps.native_addons",
|
|
162
|
-
title: "Potential native addons / node-gyp toolchain risk",
|
|
163
|
-
severity,
|
|
164
|
-
details: suspects.map((n) => `${n}@${allDeps[n] ?? ""}`.trim()),
|
|
165
|
-
hints: [
|
|
166
|
-
"Native addons often require toolchains and can be sensitive to runtime differences.",
|
|
167
|
-
"If you see install/build failures, try upgrading these packages or switching to pure-JS alternatives."
|
|
168
|
-
]
|
|
169
|
-
}
|
|
170
|
-
];
|
|
171
|
-
};
|
|
172
295
|
var detectLockfileSignals = (repo) => {
|
|
173
296
|
const { bunLock, bunLockb, npmLock, yarnLock, pnpmLock } = repo.lockfiles;
|
|
174
297
|
if (bunLock || bunLockb)
|
|
@@ -208,6 +331,153 @@ var detectLockfileSignals = (repo) => {
|
|
|
208
331
|
}
|
|
209
332
|
];
|
|
210
333
|
};
|
|
334
|
+
var detectRuntimeApiRisks = (repo) => {
|
|
335
|
+
const findings = [];
|
|
336
|
+
if (repo.packageJson?.engines?.node) {
|
|
337
|
+
const nodeVersion = repo.packageJson.engines.node;
|
|
338
|
+
const match = nodeVersion.match(/>=?(\d+)/);
|
|
339
|
+
if (match && match[1]) {
|
|
340
|
+
const minVersion = parseInt(match[1], 10);
|
|
341
|
+
if (minVersion < 18) {
|
|
342
|
+
findings.push({
|
|
343
|
+
id: "runtime.node_version",
|
|
344
|
+
title: "Node.js version requirement is below Bun's baseline (v18+)",
|
|
345
|
+
severity: "yellow",
|
|
346
|
+
details: [`engines.node: ${nodeVersion}`],
|
|
347
|
+
hints: [
|
|
348
|
+
"Bun targets Node 18+ compatibility. Packages requiring older Node versions may need updates.",
|
|
349
|
+
"Check if packages have updates or if version constraints can be relaxed."
|
|
350
|
+
]
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const allDeps = { ...repo.dependencies, ...repo.devDependencies, ...repo.optionalDependencies };
|
|
356
|
+
const deps = Object.keys(allDeps);
|
|
357
|
+
const devToolHits = deps.filter((d) => DEV_TOOL_SUSPECTS.includes(d) || deps.some((x) => x.startsWith(`${d}/`)));
|
|
358
|
+
const relevantDevTools = devToolHits.filter((d) => allDeps[d]);
|
|
359
|
+
if (relevantDevTools.length > 0) {
|
|
360
|
+
findings.push({
|
|
361
|
+
id: "runtime.dev_tools",
|
|
362
|
+
title: "Dev tools that may need Bun compatibility checks",
|
|
363
|
+
severity: "yellow",
|
|
364
|
+
details: relevantDevTools.map((d) => `${d}@${allDeps[d]}`),
|
|
365
|
+
hints: [
|
|
366
|
+
"Testing frameworks like jest/vitest may work, but consider migrating to bun:test for optimal performance.",
|
|
367
|
+
"Build tools like webpack/esbuild/vite typically work with Bun, but verify your build pipeline.",
|
|
368
|
+
"Check documentation for each tool's Bun compatibility status."
|
|
369
|
+
]
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const runtimeToolHits = deps.filter((d) => RUNTIME_TOOL_SUSPECTS.includes(d) || deps.some((x) => x.startsWith(`${d}/`)));
|
|
373
|
+
const relevantRuntimeTools = runtimeToolHits.filter((d) => allDeps[d]);
|
|
374
|
+
if (relevantRuntimeTools.length > 0) {
|
|
375
|
+
findings.push({
|
|
376
|
+
id: "runtime.build_tools",
|
|
377
|
+
title: "Runtime/build tools that may need Bun compatibility verification",
|
|
378
|
+
severity: "yellow",
|
|
379
|
+
details: relevantRuntimeTools.map((d) => `${d}@${allDeps[d]}`),
|
|
380
|
+
hints: [
|
|
381
|
+
"Tools like ts-node/tsx work with Bun, but verify your TypeScript configuration.",
|
|
382
|
+
"Bundlers like webpack/vite/esbuild typically work with Bun, but verify your build scripts.",
|
|
383
|
+
"Consider switching to Bun's native build capabilities for improved performance."
|
|
384
|
+
]
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
const scriptNames = Object.keys(repo.scripts);
|
|
388
|
+
const tsRuntimeScripts = scriptNames.filter((k) => {
|
|
389
|
+
const script = repo.scripts[k]?.toLowerCase() || "";
|
|
390
|
+
return script.includes("ts-node") || script.includes("tsx") || script.includes("babel-node");
|
|
391
|
+
});
|
|
392
|
+
if (tsRuntimeScripts.length > 0) {
|
|
393
|
+
findings.push({
|
|
394
|
+
id: "runtime.ts_execution",
|
|
395
|
+
title: "Scripts use TypeScript runtime execution (ts-node/tsx/babel-node)",
|
|
396
|
+
severity: "yellow",
|
|
397
|
+
details: tsRuntimeScripts.map((k) => `${k}: ${repo.scripts[k]}`),
|
|
398
|
+
hints: [
|
|
399
|
+
"Bun supports TypeScript natively, but verify tsconfig compatibility.",
|
|
400
|
+
"Consider migrating scripts to use `bun run` directly with TypeScript files.",
|
|
401
|
+
"Test execution of affected scripts with Bun before full migration."
|
|
402
|
+
]
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
return findings;
|
|
406
|
+
};
|
|
407
|
+
var detectPmAssumptions = (repo) => {
|
|
408
|
+
const findings = [];
|
|
409
|
+
const scriptNames = Object.keys(repo.scripts);
|
|
410
|
+
const pmSpecificHits = [];
|
|
411
|
+
for (const scriptName of scriptNames) {
|
|
412
|
+
const script = repo.scripts[scriptName] || "";
|
|
413
|
+
const lowerScript = script.toLowerCase();
|
|
414
|
+
for (const cmd of PM_SPECIFIC_COMMANDS) {
|
|
415
|
+
if (lowerScript.includes(cmd.toLowerCase())) {
|
|
416
|
+
pmSpecificHits.push({ name: scriptName, command: cmd });
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (pmSpecificHits.length > 0) {
|
|
422
|
+
findings.push({
|
|
423
|
+
id: "scripts.pm_assumptions",
|
|
424
|
+
title: "Scripts contain package-manager-specific commands",
|
|
425
|
+
severity: "yellow",
|
|
426
|
+
details: pmSpecificHits.map((h) => `${h.name}: uses "${h.command}"`),
|
|
427
|
+
hints: [
|
|
428
|
+
"Package-manager-specific commands may need adaptation for Bun.",
|
|
429
|
+
"Test each affected script with Bun to ensure compatibility.",
|
|
430
|
+
"Consider rewriting scripts to be package-manager agnostic where possible."
|
|
431
|
+
]
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
return findings;
|
|
435
|
+
};
|
|
436
|
+
var detectNativeAddonRiskV2 = (repo, config) => {
|
|
437
|
+
const allDeps = {
|
|
438
|
+
...repo.dependencies,
|
|
439
|
+
...repo.devDependencies,
|
|
440
|
+
...repo.optionalDependencies
|
|
441
|
+
};
|
|
442
|
+
const names = Object.keys(allDeps);
|
|
443
|
+
const allowlist = config?.nativeAddonAllowlist || [];
|
|
444
|
+
const suspects = names.filter((n) => {
|
|
445
|
+
if (allowlist.includes(n))
|
|
446
|
+
return false;
|
|
447
|
+
const inList = NATIVE_SUSPECTS_V2.includes(n);
|
|
448
|
+
if (inList)
|
|
449
|
+
return true;
|
|
450
|
+
const keywords = ["@napi-rs/", "napi-rs", "node-napi", "neon", "node-gyp", "prebuild", "ffi", "bindings", "native", "native-module"];
|
|
451
|
+
const keywordMatch = includesAny(n, keywords);
|
|
452
|
+
if (keywordMatch) {
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
return false;
|
|
456
|
+
});
|
|
457
|
+
const scriptNames = Object.keys(repo.scripts);
|
|
458
|
+
const hasNodeGypRebuild = scriptNames.some((k) => {
|
|
459
|
+
const script = repo.scripts[k]?.toLowerCase() || "";
|
|
460
|
+
return script.includes("node-gyp") || script.includes("node-gyp rebuild");
|
|
461
|
+
});
|
|
462
|
+
if (suspects.length === 0 && !hasNodeGypRebuild)
|
|
463
|
+
return [];
|
|
464
|
+
const hardRed = suspects.some((n) => n === "node-gyp" || n === "node-sass") || hasNodeGypRebuild;
|
|
465
|
+
const severity = hardRed ? "red" : "yellow";
|
|
466
|
+
return [
|
|
467
|
+
{
|
|
468
|
+
id: "deps.native_addons",
|
|
469
|
+
title: "Potential native addons / node-gyp toolchain risk",
|
|
470
|
+
severity,
|
|
471
|
+
details: suspects.map((n) => `${n}@${allDeps[n]}`),
|
|
472
|
+
hints: [
|
|
473
|
+
"Native addons often require toolchains and can be sensitive to runtime differences.",
|
|
474
|
+
"If you see install/build failures, try upgrading these packages or switching to pure-JS alternatives.",
|
|
475
|
+
"Some packages offer optional native modules that can be disabled via configuration.",
|
|
476
|
+
"Check if native modules are in use or just installed for optional features."
|
|
477
|
+
]
|
|
478
|
+
}
|
|
479
|
+
];
|
|
480
|
+
};
|
|
211
481
|
var summarizeSeverity = (findings, installOk, testOk) => {
|
|
212
482
|
let sev = "green";
|
|
213
483
|
for (const f of findings)
|
|
@@ -219,33 +489,276 @@ var summarizeSeverity = (findings, installOk, testOk) => {
|
|
|
219
489
|
return sev;
|
|
220
490
|
};
|
|
221
491
|
|
|
492
|
+
// src/bun_logs.ts
|
|
493
|
+
function parseInstallLogs(logs) {
|
|
494
|
+
const blockedDeps = [];
|
|
495
|
+
const trustedDepsMentioned = [];
|
|
496
|
+
const notes = [];
|
|
497
|
+
const blockedKeywords = ["blocked", "not allowed", "lifecycle script", "postinstall blocked", "prepare blocked"];
|
|
498
|
+
const trustedKeywords = ["trustedDependencies", "trusted", "trust"];
|
|
499
|
+
for (const log of logs) {
|
|
500
|
+
const lowerLog = log.toLowerCase();
|
|
501
|
+
for (const keyword of blockedKeywords) {
|
|
502
|
+
if (lowerLog.includes(keyword)) {
|
|
503
|
+
let pkgMatch = log.match(/blocked:\s+(?:lifecycle\s+script\s+(?:for\s+)?)?(@?[a-z0-9-]+\/[a-z0-9-]+|@?[a-z0-9-]+)@[\d.^]+/i);
|
|
504
|
+
if (pkgMatch && pkgMatch[1] && !blockedDeps.includes(pkgMatch[1])) {
|
|
505
|
+
blockedDeps.push(pkgMatch[1]);
|
|
506
|
+
} else {
|
|
507
|
+
const fallbackMatch = log.match(/blocked:\s+(?:lifecycle\s+script\s+(?:for\s+)?)?(@?[a-z0-9-]+\/[a-z0-9-]+|@?[a-z0-9-]+)/i);
|
|
508
|
+
if (fallbackMatch && fallbackMatch[1] && !blockedDeps.includes(fallbackMatch[1]) && !blockedKeywords.includes(fallbackMatch[1].toLowerCase())) {
|
|
509
|
+
blockedDeps.push(fallbackMatch[1]);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
for (const keyword of trustedKeywords) {
|
|
516
|
+
if (lowerLog.includes(keyword)) {
|
|
517
|
+
if (!notes.some((n) => n.toLowerCase().includes(keyword))) {
|
|
518
|
+
notes.push(log.trim());
|
|
519
|
+
}
|
|
520
|
+
const pkgMatch = log.match(/@?[a-z0-9-]+\/[a-z0-9-]+|@?[a-z0-9-]+/gi);
|
|
521
|
+
if (pkgMatch) {
|
|
522
|
+
for (const pkg of pkgMatch) {
|
|
523
|
+
if (pkg.toLowerCase() !== "trusteddependencies" && !trustedDepsMentioned.includes(pkg)) {
|
|
524
|
+
trustedDepsMentioned.push(pkg);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (lowerLog.includes("warning") || lowerLog.includes("warn")) {
|
|
532
|
+
notes.push(log.trim());
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
blockedDeps.sort((a, b) => a.localeCompare(b));
|
|
536
|
+
trustedDepsMentioned.sort((a, b) => a.localeCompare(b));
|
|
537
|
+
notes.sort();
|
|
538
|
+
return {
|
|
539
|
+
blockedDeps,
|
|
540
|
+
trustedDepsMentioned,
|
|
541
|
+
notes
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/workspaces.ts
|
|
546
|
+
import path2 from "node:path";
|
|
547
|
+
import fsSync from "node:fs";
|
|
548
|
+
function globMatch(pattern, path3) {
|
|
549
|
+
const patternParts = pattern.split("/");
|
|
550
|
+
const pathParts = path3.split("/");
|
|
551
|
+
let patternIdx = 0;
|
|
552
|
+
let pathIdx = 0;
|
|
553
|
+
while (patternIdx < patternParts.length && pathIdx < pathParts.length) {
|
|
554
|
+
const patternPart = patternParts[patternIdx];
|
|
555
|
+
const pathPart = pathParts[pathIdx];
|
|
556
|
+
if (patternPart === "**") {
|
|
557
|
+
if (patternIdx === patternParts.length - 1) {
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
const nextPatternPart = patternParts[patternIdx + 1];
|
|
561
|
+
while (pathIdx < pathParts.length && pathParts[pathIdx] !== nextPatternPart) {
|
|
562
|
+
pathIdx++;
|
|
563
|
+
}
|
|
564
|
+
patternIdx++;
|
|
565
|
+
} else if (patternPart === "*") {
|
|
566
|
+
patternIdx++;
|
|
567
|
+
pathIdx++;
|
|
568
|
+
} else {
|
|
569
|
+
if (patternPart !== pathPart) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
patternIdx++;
|
|
573
|
+
pathIdx++;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (patternIdx < patternParts.length) {
|
|
577
|
+
const remaining = patternParts.slice(patternIdx);
|
|
578
|
+
if (remaining.length === 1 && remaining[0] === "**") {
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return patternIdx === patternParts.length && pathIdx === pathParts.length;
|
|
583
|
+
}
|
|
584
|
+
function discoverFromWorkspaces(rootPath, workspaces) {
|
|
585
|
+
const patterns = Array.isArray(workspaces) ? workspaces : workspaces.packages;
|
|
586
|
+
const packages = [];
|
|
587
|
+
for (const pattern of patterns) {
|
|
588
|
+
const patternPath = path2.resolve(rootPath, pattern);
|
|
589
|
+
if (pattern.includes("/*") && !pattern.includes("/**")) {
|
|
590
|
+
try {
|
|
591
|
+
const baseDir = path2.dirname(patternPath);
|
|
592
|
+
const patternName = path2.basename(patternPath);
|
|
593
|
+
const entries = fsSync.readdirSync(baseDir, { withFileTypes: true });
|
|
594
|
+
for (const entry of entries) {
|
|
595
|
+
if (entry.isDirectory() && globMatch(patternName, entry.name)) {
|
|
596
|
+
const packagePath = path2.join(baseDir, entry.name);
|
|
597
|
+
const pkgJsonPath = path2.join(packagePath, "package.json");
|
|
598
|
+
if (fileExistsSync(pkgJsonPath)) {
|
|
599
|
+
packages.push(packagePath);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
} catch {}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return packages;
|
|
607
|
+
}
|
|
608
|
+
function fileExistsSync(filePath) {
|
|
609
|
+
try {
|
|
610
|
+
fsSync.accessSync(filePath);
|
|
611
|
+
return true;
|
|
612
|
+
} catch {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
async function discoverWorkspaces(rootPath) {
|
|
617
|
+
const packages = [];
|
|
618
|
+
const rootPkgJson = path2.join(rootPath, "package.json");
|
|
619
|
+
if (!await fileExists(rootPkgJson)) {
|
|
620
|
+
return packages;
|
|
621
|
+
}
|
|
622
|
+
const rootPkg = await readJsonFile(rootPkgJson);
|
|
623
|
+
if (!rootPkg.workspaces && !rootPkg.packages) {
|
|
624
|
+
return packages;
|
|
625
|
+
}
|
|
626
|
+
const workspaces = rootPkg.workspaces || rootPkg.packages;
|
|
627
|
+
if (!workspaces) {
|
|
628
|
+
return packages;
|
|
629
|
+
}
|
|
630
|
+
const packagePaths = discoverFromWorkspaces(rootPath, workspaces);
|
|
631
|
+
for (const pkgPath of packagePaths) {
|
|
632
|
+
const pkgJsonPath = path2.join(pkgPath, "package.json");
|
|
633
|
+
try {
|
|
634
|
+
const pkg = await readJsonFile(pkgJsonPath);
|
|
635
|
+
if (pkg.name) {
|
|
636
|
+
packages.push({
|
|
637
|
+
name: pkg.name,
|
|
638
|
+
path: pkgPath,
|
|
639
|
+
packageJsonPath: pkgJsonPath
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
} catch {
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
647
|
+
return packages;
|
|
648
|
+
}
|
|
649
|
+
async function hasWorkspaces(rootPath) {
|
|
650
|
+
const rootPkgJson = path2.join(rootPath, "package.json");
|
|
651
|
+
if (!await fileExists(rootPkgJson)) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
const rootPkg = await readJsonFile(rootPkgJson);
|
|
655
|
+
return Boolean(rootPkg.workspaces || rootPkg.packages);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/config.ts
|
|
659
|
+
import path3 from "node:path";
|
|
660
|
+
var CONFIG_FILE_NAME = "bun-ready.config.json";
|
|
661
|
+
async function readConfig(rootPath) {
|
|
662
|
+
const configPath = path3.join(rootPath, CONFIG_FILE_NAME);
|
|
663
|
+
if (!await fileExists(configPath)) {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
try {
|
|
667
|
+
const config = await readJsonFile(configPath);
|
|
668
|
+
return validateConfig(config);
|
|
669
|
+
} catch (error) {
|
|
670
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
671
|
+
process.stderr.write(`Warning: Invalid ${CONFIG_FILE_NAME}: ${msg}
|
|
672
|
+
`);
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function validateConfig(config) {
|
|
677
|
+
if (!config || typeof config !== "object") {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
const cfg = config;
|
|
681
|
+
const result = {};
|
|
682
|
+
if (Array.isArray(cfg.ignorePackages)) {
|
|
683
|
+
const validIgnore = cfg.ignorePackages.filter((p) => typeof p === "string");
|
|
684
|
+
if (validIgnore.length > 0) {
|
|
685
|
+
result.ignorePackages = validIgnore;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (Array.isArray(cfg.ignoreFindings)) {
|
|
689
|
+
const validIgnore = cfg.ignoreFindings.filter((f) => typeof f === "string");
|
|
690
|
+
if (validIgnore.length > 0) {
|
|
691
|
+
result.ignoreFindings = validIgnore;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (Array.isArray(cfg.nativeAddonAllowlist)) {
|
|
695
|
+
const validAllowlist = cfg.nativeAddonAllowlist.filter((p) => typeof p === "string");
|
|
696
|
+
if (validAllowlist.length > 0) {
|
|
697
|
+
result.nativeAddonAllowlist = validAllowlist;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (typeof cfg.failOn === "string") {
|
|
701
|
+
const validFailOn = ["green", "yellow", "red"].includes(cfg.failOn);
|
|
702
|
+
if (validFailOn) {
|
|
703
|
+
result.failOn = cfg.failOn;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (Object.keys(result).length === 0) {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
return result;
|
|
710
|
+
}
|
|
711
|
+
function mergeConfigWithOpts(config, opts) {
|
|
712
|
+
if (!config && !opts.failOn) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
const result = {
|
|
716
|
+
...config || {}
|
|
717
|
+
};
|
|
718
|
+
if (opts.failOn) {
|
|
719
|
+
result.failOn = opts.failOn;
|
|
720
|
+
}
|
|
721
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
722
|
+
}
|
|
723
|
+
|
|
222
724
|
// src/analyze.ts
|
|
223
|
-
|
|
224
|
-
const packageJsonPath =
|
|
725
|
+
async function readRepoInfo(packagePath) {
|
|
726
|
+
const packageJsonPath = path4.join(packagePath, "package.json");
|
|
225
727
|
const pkg = await readJsonFile(packageJsonPath);
|
|
226
728
|
const scripts = pkg.scripts ?? {};
|
|
227
729
|
const dependencies = pkg.dependencies ?? {};
|
|
228
730
|
const devDependencies = pkg.devDependencies ?? {};
|
|
229
731
|
const optionalDependencies = pkg.optionalDependencies ?? {};
|
|
230
|
-
const hasWorkspaces = Boolean(pkg.workspaces);
|
|
231
732
|
const lockfiles = {
|
|
232
|
-
bunLock: await fileExists(
|
|
233
|
-
bunLockb: await fileExists(
|
|
234
|
-
npmLock: await fileExists(
|
|
235
|
-
yarnLock: await fileExists(
|
|
236
|
-
pnpmLock: await fileExists(
|
|
733
|
+
bunLock: await fileExists(path4.join(packagePath, "bun.lock")),
|
|
734
|
+
bunLockb: await fileExists(path4.join(packagePath, "bun.lockb")),
|
|
735
|
+
npmLock: await fileExists(path4.join(packagePath, "package-lock.json")),
|
|
736
|
+
yarnLock: await fileExists(path4.join(packagePath, "yarn.lock")),
|
|
737
|
+
pnpmLock: await fileExists(path4.join(packagePath, "pnpm-lock.yaml"))
|
|
237
738
|
};
|
|
238
|
-
return {
|
|
239
|
-
}
|
|
240
|
-
|
|
739
|
+
return { pkg, scripts, dependencies, devDependencies, optionalDependencies, lockfiles };
|
|
740
|
+
}
|
|
741
|
+
async function copyIfExists(from, to) {
|
|
241
742
|
try {
|
|
242
743
|
await fs2.copyFile(from, to);
|
|
243
744
|
} catch {
|
|
244
745
|
return;
|
|
245
746
|
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const
|
|
747
|
+
}
|
|
748
|
+
async function runBunInstallDryRun(packagePath) {
|
|
749
|
+
const { checkBunAvailable: checkBunAvailable2 } = await Promise.resolve().then(() => (init_bun_check(), exports_bun_check));
|
|
750
|
+
const bunCheck = await checkBunAvailable2();
|
|
751
|
+
if (!bunCheck.available) {
|
|
752
|
+
const skipReason = bunCheck.error;
|
|
753
|
+
return {
|
|
754
|
+
ok: false,
|
|
755
|
+
summary: `Skipped: ${skipReason}`,
|
|
756
|
+
logs: [],
|
|
757
|
+
installAnalysis: { blockedDeps: [], trustedDepsMentioned: [], notes: [] },
|
|
758
|
+
skipReason
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
const base = await fs2.mkdtemp(path4.join(os.tmpdir(), "bun-ready-"));
|
|
249
762
|
const cleanup = async () => {
|
|
250
763
|
try {
|
|
251
764
|
await fs2.rm(base, { recursive: true, force: true });
|
|
@@ -254,42 +767,160 @@ var runBunInstallDryRun = async (repoPath) => {
|
|
|
254
767
|
}
|
|
255
768
|
};
|
|
256
769
|
try {
|
|
257
|
-
await copyIfExists(
|
|
258
|
-
await copyIfExists(
|
|
259
|
-
await copyIfExists(
|
|
260
|
-
await copyIfExists(
|
|
261
|
-
await copyIfExists(
|
|
262
|
-
await copyIfExists(
|
|
770
|
+
await copyIfExists(path4.join(packagePath, "package.json"), path4.join(base, "package.json"));
|
|
771
|
+
await copyIfExists(path4.join(packagePath, "bun.lock"), path4.join(base, "bun.lock"));
|
|
772
|
+
await copyIfExists(path4.join(packagePath, "bun.lockb"), path4.join(base, "bun.lockb"));
|
|
773
|
+
await copyIfExists(path4.join(packagePath, "package-lock.json"), path4.join(base, "package-lock.json"));
|
|
774
|
+
await copyIfExists(path4.join(packagePath, "yarn.lock"), path4.join(base, "yarn.lock"));
|
|
775
|
+
await copyIfExists(path4.join(packagePath, "pnpm-lock.yaml"), path4.join(base, "pnpm-lock.yaml"));
|
|
263
776
|
const res = await exec("bun", ["install", "--dry-run"], base);
|
|
264
777
|
const combined = [...res.stdout ? res.stdout.split(`
|
|
265
778
|
`) : [], ...res.stderr ? res.stderr.split(`
|
|
266
779
|
`) : []].filter((l) => l.trim().length > 0);
|
|
267
780
|
const logs = truncateLines(combined, 60);
|
|
268
|
-
|
|
781
|
+
const installAnalysis = parseInstallLogs(logs);
|
|
782
|
+
return res.code === 0 ? { ok: true, summary: "bun install --dry-run succeeded", logs, installAnalysis } : { ok: false, summary: `bun install --dry-run failed (exit ${res.code})`, logs, installAnalysis };
|
|
269
783
|
} finally {
|
|
270
784
|
await cleanup();
|
|
271
785
|
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const t =
|
|
786
|
+
}
|
|
787
|
+
function shouldRunBunTest(scripts) {
|
|
788
|
+
const t = scripts["test"];
|
|
275
789
|
if (!t)
|
|
276
790
|
return false;
|
|
277
791
|
return t.toLowerCase().includes("bun test") || t.toLowerCase().trim() === "bun test";
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const
|
|
792
|
+
}
|
|
793
|
+
async function runBunTest(packagePath) {
|
|
794
|
+
const { checkBunAvailable: checkBunAvailable2 } = await Promise.resolve().then(() => (init_bun_check(), exports_bun_check));
|
|
795
|
+
const bunCheck = await checkBunAvailable2();
|
|
796
|
+
if (!bunCheck.available) {
|
|
797
|
+
const skipReason = bunCheck.error;
|
|
798
|
+
return {
|
|
799
|
+
ok: false,
|
|
800
|
+
summary: `Skipped: ${skipReason}`,
|
|
801
|
+
logs: [],
|
|
802
|
+
skipReason
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
const res = await exec("bun", ["test"], packagePath);
|
|
281
806
|
const combined = [...res.stdout ? res.stdout.split(`
|
|
282
807
|
`) : [], ...res.stderr ? res.stderr.split(`
|
|
283
808
|
`) : []].filter((l) => l.trim().length > 0);
|
|
284
809
|
const logs = truncateLines(combined, 120);
|
|
285
810
|
return res.code === 0 ? { ok: true, summary: "bun test succeeded", logs } : { ok: false, summary: `bun test failed (exit ${res.code})`, logs };
|
|
286
|
-
}
|
|
287
|
-
|
|
811
|
+
}
|
|
812
|
+
function filterFindings(findings, config) {
|
|
813
|
+
const ignoreList = config?.ignoreFindings;
|
|
814
|
+
if (!ignoreList || ignoreList.length === 0) {
|
|
815
|
+
return findings;
|
|
816
|
+
}
|
|
817
|
+
return findings.filter((f) => !ignoreList.includes(f.id));
|
|
818
|
+
}
|
|
819
|
+
async function analyzeSinglePackage(packagePath, opts, config, pkgName) {
|
|
820
|
+
const info = await readRepoInfo(packagePath);
|
|
821
|
+
const name = pkgName || info.pkg.name || path4.basename(packagePath);
|
|
822
|
+
let findings = [
|
|
823
|
+
...detectLockfileSignals({ packageJsonPath: packagePath, lockfiles: info.lockfiles, scripts: info.scripts, dependencies: info.dependencies, devDependencies: info.devDependencies, optionalDependencies: info.optionalDependencies, hasWorkspaces: false, packageJson: info.pkg }),
|
|
824
|
+
...detectScriptRisks({ packageJsonPath: packagePath, lockfiles: info.lockfiles, scripts: info.scripts, dependencies: info.dependencies, devDependencies: info.devDependencies, optionalDependencies: info.optionalDependencies, hasWorkspaces: false, packageJson: info.pkg }),
|
|
825
|
+
...detectNativeAddonRiskV2({ packageJsonPath: packagePath, lockfiles: info.lockfiles, scripts: info.scripts, dependencies: info.dependencies, devDependencies: info.devDependencies, optionalDependencies: info.optionalDependencies, hasWorkspaces: false, packageJson: info.pkg }, config || undefined),
|
|
826
|
+
...detectRuntimeApiRisks({ packageJsonPath: packagePath, lockfiles: info.lockfiles, scripts: info.scripts, dependencies: info.dependencies, devDependencies: info.devDependencies, optionalDependencies: info.optionalDependencies, hasWorkspaces: false, packageJson: info.pkg }),
|
|
827
|
+
...detectPmAssumptions({ packageJsonPath: packagePath, lockfiles: info.lockfiles, scripts: info.scripts, dependencies: info.dependencies, devDependencies: info.devDependencies, optionalDependencies: info.optionalDependencies, hasWorkspaces: false, packageJson: info.pkg })
|
|
828
|
+
];
|
|
829
|
+
findings = filterFindings(findings, config);
|
|
830
|
+
let install = null;
|
|
831
|
+
let installOk = null;
|
|
832
|
+
if (opts.runInstall) {
|
|
833
|
+
const installResult = await runBunInstallDryRun(packagePath);
|
|
834
|
+
install = {
|
|
835
|
+
ok: installResult.ok,
|
|
836
|
+
summary: installResult.summary,
|
|
837
|
+
logs: installResult.logs,
|
|
838
|
+
...installResult.skipReason !== undefined ? { skipReason: installResult.skipReason } : {}
|
|
839
|
+
};
|
|
840
|
+
installOk = installResult.ok;
|
|
841
|
+
if (installResult.installAnalysis.blockedDeps.length > 0) {
|
|
842
|
+
findings.push({
|
|
843
|
+
id: "install.blocked_scripts",
|
|
844
|
+
title: "Lifecycle scripts blocked by Bun",
|
|
845
|
+
severity: "red",
|
|
846
|
+
details: installResult.installAnalysis.blockedDeps,
|
|
847
|
+
hints: [
|
|
848
|
+
"Bun blocks lifecycle scripts of dependencies unless they are in trustedDependencies.",
|
|
849
|
+
"Add the blocked packages to trustedDependencies in root package.json.",
|
|
850
|
+
"Review if these packages are necessary or can be replaced with alternatives."
|
|
851
|
+
]
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
if (installResult.installAnalysis.trustedDepsMentioned.length > 0) {
|
|
855
|
+
findings.push({
|
|
856
|
+
id: "install.trusted_deps",
|
|
857
|
+
title: "Trusted dependencies mentioned in install output",
|
|
858
|
+
severity: "yellow",
|
|
859
|
+
details: installResult.installAnalysis.trustedDepsMentioned,
|
|
860
|
+
hints: [
|
|
861
|
+
"Review the trustedDependencies configuration in your package.json.",
|
|
862
|
+
"Consider adding packages to trustedDependencies if you trust them."
|
|
863
|
+
]
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
let test = null;
|
|
868
|
+
let testOk = null;
|
|
869
|
+
if (opts.runTest && shouldRunBunTest(info.scripts)) {
|
|
870
|
+
const testResult = await runBunTest(packagePath);
|
|
871
|
+
test = {
|
|
872
|
+
ok: testResult.ok,
|
|
873
|
+
summary: testResult.summary,
|
|
874
|
+
logs: testResult.logs,
|
|
875
|
+
...testResult.skipReason !== undefined ? { skipReason: testResult.skipReason } : {}
|
|
876
|
+
};
|
|
877
|
+
testOk = testResult.ok;
|
|
878
|
+
}
|
|
879
|
+
const severity = summarizeSeverity(findings, installOk, testOk);
|
|
880
|
+
const summaryLines = [];
|
|
881
|
+
summaryLines.push(`Lockfiles: ${info.lockfiles.bunLock || info.lockfiles.bunLockb ? "bun" : "non-bun or missing"}`);
|
|
882
|
+
summaryLines.push(`Lifecycle scripts: ${Object.keys(info.scripts).some((k) => ["postinstall", "prepare", "preinstall", "install"].includes(k)) ? "present" : "none"}`);
|
|
883
|
+
summaryLines.push(`Native addon risk: ${findings.some((f) => f.id === "deps.native_addons") ? "yes" : "no"}`);
|
|
884
|
+
summaryLines.push(`bun install dry-run: ${install ? install.ok ? "ok" : "failed" : "skipped"}`);
|
|
885
|
+
summaryLines.push(`bun test: ${test ? test.ok ? "ok" : "failed" : "skipped"}`);
|
|
886
|
+
return {
|
|
887
|
+
name,
|
|
888
|
+
path: packagePath,
|
|
889
|
+
severity,
|
|
890
|
+
summaryLines,
|
|
891
|
+
findings,
|
|
892
|
+
install,
|
|
893
|
+
test,
|
|
894
|
+
scripts: info.scripts,
|
|
895
|
+
dependencies: info.dependencies,
|
|
896
|
+
devDependencies: info.devDependencies,
|
|
897
|
+
optionalDependencies: info.optionalDependencies,
|
|
898
|
+
lockfiles: info.lockfiles
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function aggregateSeverity(packages, overallSeverity) {
|
|
902
|
+
if (overallSeverity === "red")
|
|
903
|
+
return "red";
|
|
904
|
+
if (overallSeverity === "yellow")
|
|
905
|
+
return "yellow";
|
|
906
|
+
for (const pkg of packages) {
|
|
907
|
+
if (pkg.severity === "red")
|
|
908
|
+
return "red";
|
|
909
|
+
}
|
|
910
|
+
for (const pkg of packages) {
|
|
911
|
+
if (pkg.severity === "yellow")
|
|
912
|
+
return "yellow";
|
|
913
|
+
}
|
|
914
|
+
return "green";
|
|
915
|
+
}
|
|
916
|
+
async function analyzeRepoOverall(opts) {
|
|
288
917
|
const repoPath = normalizeRepoPath(opts.repoPath);
|
|
289
|
-
const packageJsonPath =
|
|
918
|
+
const packageJsonPath = path4.join(repoPath, "package.json");
|
|
290
919
|
const hasPkg = await fileExists(packageJsonPath);
|
|
920
|
+
const config = await readConfig(repoPath);
|
|
291
921
|
if (!hasPkg) {
|
|
292
922
|
return {
|
|
923
|
+
version: "0.2",
|
|
293
924
|
severity: "red",
|
|
294
925
|
summaryLines: ["package.json not found"],
|
|
295
926
|
findings: [
|
|
@@ -305,35 +936,90 @@ var analyzeRepo = async (opts) => {
|
|
|
305
936
|
test: null,
|
|
306
937
|
repo: {
|
|
307
938
|
packageJsonPath,
|
|
939
|
+
hasWorkspaces: false,
|
|
940
|
+
rootPackage: { name: "", version: "" },
|
|
308
941
|
lockfiles: { bunLock: false, bunLockb: false, npmLock: false, yarnLock: false, pnpmLock: false },
|
|
309
942
|
scripts: {},
|
|
310
943
|
dependencies: {},
|
|
311
944
|
devDependencies: {},
|
|
312
|
-
optionalDependencies: {}
|
|
313
|
-
|
|
314
|
-
|
|
945
|
+
optionalDependencies: {}
|
|
946
|
+
},
|
|
947
|
+
packages: [],
|
|
948
|
+
config
|
|
315
949
|
};
|
|
316
950
|
}
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
...
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
951
|
+
const rootInfo = await readRepoInfo(repoPath);
|
|
952
|
+
const rootHasWorkspaces = await hasWorkspaces(repoPath);
|
|
953
|
+
const workspacePackages = [];
|
|
954
|
+
if (rootHasWorkspaces) {
|
|
955
|
+
workspacePackages.push(...await discoverWorkspaces(repoPath));
|
|
956
|
+
}
|
|
957
|
+
let packagesToAnalyze = [];
|
|
958
|
+
let packagesToReport = [];
|
|
959
|
+
if (opts.scope === "root") {
|
|
960
|
+
packagesToAnalyze = [repoPath];
|
|
961
|
+
packagesToReport = [repoPath];
|
|
962
|
+
} else if (opts.scope === "packages") {
|
|
963
|
+
packagesToAnalyze = workspacePackages.map((wp) => wp.path);
|
|
964
|
+
packagesToReport = workspacePackages.map((wp) => wp.path);
|
|
965
|
+
} else {
|
|
966
|
+
packagesToAnalyze = [repoPath, ...workspacePackages.map((wp) => wp.path)];
|
|
967
|
+
packagesToReport = [repoPath, ...workspacePackages.map((wp) => wp.path)];
|
|
968
|
+
}
|
|
969
|
+
const ignorePackages = config?.ignorePackages;
|
|
970
|
+
if (ignorePackages && ignorePackages.length > 0) {
|
|
971
|
+
packagesToAnalyze = packagesToAnalyze.filter((p) => {
|
|
972
|
+
return !ignorePackages.some((ignore) => p.includes(ignore));
|
|
973
|
+
});
|
|
974
|
+
packagesToReport = packagesToReport.filter((p) => {
|
|
975
|
+
return !ignorePackages.some((ignore) => p.includes(ignore));
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
const packages = [];
|
|
979
|
+
for (const packagePath of packagesToAnalyze) {
|
|
980
|
+
const wp = workspacePackages.find((w) => w.path === packagePath);
|
|
981
|
+
const analysis = await analyzeSinglePackage(packagePath, opts, config, wp?.name);
|
|
982
|
+
packages.push(analysis);
|
|
983
|
+
}
|
|
984
|
+
const rootAnalysis = packages.find((p) => p.path === repoPath);
|
|
985
|
+
let overallSeverity = "green";
|
|
986
|
+
if (rootAnalysis) {
|
|
987
|
+
overallSeverity = rootAnalysis.severity;
|
|
988
|
+
}
|
|
989
|
+
overallSeverity = aggregateSeverity(packages, overallSeverity);
|
|
990
|
+
const overallFindings = rootAnalysis ? rootAnalysis.findings : [];
|
|
991
|
+
const overallSummaryLines = [];
|
|
992
|
+
overallSummaryLines.push(`Total packages analyzed: ${packages.length}`);
|
|
993
|
+
overallSummaryLines.push(`Workspaces detected: ${rootHasWorkspaces ? "yes" : "no"}`);
|
|
994
|
+
if (rootHasWorkspaces) {
|
|
995
|
+
overallSummaryLines.push(`Workspace packages: ${workspacePackages.length}`);
|
|
996
|
+
}
|
|
997
|
+
overallSummaryLines.push(`Root package severity: ${rootAnalysis ? rootAnalysis.severity : "unknown"}`);
|
|
998
|
+
overallSummaryLines.push(`Overall severity: ${overallSeverity}`);
|
|
999
|
+
return {
|
|
1000
|
+
version: "0.2",
|
|
1001
|
+
severity: overallSeverity,
|
|
1002
|
+
summaryLines: overallSummaryLines,
|
|
1003
|
+
findings: overallFindings,
|
|
1004
|
+
install: rootAnalysis ? rootAnalysis.install : null,
|
|
1005
|
+
test: rootAnalysis ? rootAnalysis.test : null,
|
|
1006
|
+
repo: {
|
|
1007
|
+
packageJsonPath,
|
|
1008
|
+
hasWorkspaces: rootHasWorkspaces,
|
|
1009
|
+
rootPackage: {
|
|
1010
|
+
name: rootInfo.pkg.name || "",
|
|
1011
|
+
version: rootInfo.pkg.version || ""
|
|
1012
|
+
},
|
|
1013
|
+
lockfiles: rootInfo.lockfiles,
|
|
1014
|
+
scripts: rootInfo.scripts,
|
|
1015
|
+
dependencies: rootInfo.dependencies,
|
|
1016
|
+
devDependencies: rootInfo.devDependencies,
|
|
1017
|
+
optionalDependencies: rootInfo.optionalDependencies
|
|
1018
|
+
},
|
|
1019
|
+
packages,
|
|
1020
|
+
config
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
337
1023
|
|
|
338
1024
|
// src/report_md.ts
|
|
339
1025
|
var badge = (s) => {
|
|
@@ -343,7 +1029,24 @@ var badge = (s) => {
|
|
|
343
1029
|
return "\uD83D\uDFE1 YELLOW";
|
|
344
1030
|
return "\uD83D\uDD34 RED";
|
|
345
1031
|
};
|
|
346
|
-
var
|
|
1032
|
+
var getTopFindings = (pkg, count = 3) => {
|
|
1033
|
+
const sorted = [...pkg.findings].sort((a, b) => {
|
|
1034
|
+
const severityOrder = { red: 0, yellow: 1, green: 2 };
|
|
1035
|
+
if (severityOrder[a.severity] !== severityOrder[b.severity]) {
|
|
1036
|
+
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
1037
|
+
}
|
|
1038
|
+
return a.id.localeCompare(b.id);
|
|
1039
|
+
});
|
|
1040
|
+
return sorted.slice(0, count).map((f) => `${badge(f.severity)} ${f.title}`);
|
|
1041
|
+
};
|
|
1042
|
+
var packageRow = (pkg) => {
|
|
1043
|
+
const name = pkg.name;
|
|
1044
|
+
const path5 = pkg.path.replace(/\\/g, "/");
|
|
1045
|
+
const severity = badge(pkg.severity);
|
|
1046
|
+
const topFindings = getTopFindings(pkg, 2).join(", ") || "No issues";
|
|
1047
|
+
return `| ${name} | \`${path5}\` | ${severity} | ${topFindings} |`;
|
|
1048
|
+
};
|
|
1049
|
+
function renderMarkdown(r) {
|
|
347
1050
|
const lines = [];
|
|
348
1051
|
lines.push(`# bun-ready report`);
|
|
349
1052
|
lines.push(``);
|
|
@@ -353,77 +1056,157 @@ var renderMarkdown = (r) => {
|
|
|
353
1056
|
for (const l of r.summaryLines)
|
|
354
1057
|
lines.push(`- ${l}`);
|
|
355
1058
|
lines.push(``);
|
|
356
|
-
|
|
1059
|
+
if (r.version) {
|
|
1060
|
+
lines.push(`**Report version:** ${r.version}`);
|
|
1061
|
+
lines.push(``);
|
|
1062
|
+
}
|
|
1063
|
+
if (r.config) {
|
|
1064
|
+
lines.push(`**Configuration:**`);
|
|
1065
|
+
const configInfo = [];
|
|
1066
|
+
if (r.config.ignorePackages && r.config.ignorePackages.length > 0) {
|
|
1067
|
+
configInfo.push(`Ignored packages: ${r.config.ignorePackages.join(", ")}`);
|
|
1068
|
+
}
|
|
1069
|
+
if (r.config.ignoreFindings && r.config.ignoreFindings.length > 0) {
|
|
1070
|
+
configInfo.push(`Ignored findings: ${r.config.ignoreFindings.join(", ")}`);
|
|
1071
|
+
}
|
|
1072
|
+
if (r.config.nativeAddonAllowlist && r.config.nativeAddonAllowlist.length > 0) {
|
|
1073
|
+
configInfo.push(`Native addon allowlist: ${r.config.nativeAddonAllowlist.join(", ")}`);
|
|
1074
|
+
}
|
|
1075
|
+
if (r.config.failOn) {
|
|
1076
|
+
configInfo.push(`Fail on: ${r.config.failOn}`);
|
|
1077
|
+
}
|
|
1078
|
+
if (configInfo.length > 0) {
|
|
1079
|
+
for (const info of configInfo) {
|
|
1080
|
+
lines.push(`- ${info}`);
|
|
1081
|
+
}
|
|
1082
|
+
} else {
|
|
1083
|
+
lines.push(`- Using default configuration`);
|
|
1084
|
+
}
|
|
1085
|
+
lines.push(``);
|
|
1086
|
+
}
|
|
1087
|
+
lines.push(`## Root Package`);
|
|
357
1088
|
lines.push(`- Path: \`${r.repo.packageJsonPath.replace(/\\/g, "/")}\``);
|
|
358
1089
|
lines.push(`- Workspaces: ${r.repo.hasWorkspaces ? "yes" : "no"}`);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
lock.push("bun.lock");
|
|
362
|
-
if (r.repo.lockfiles.bunLockb)
|
|
363
|
-
lock.push("bun.lockb");
|
|
364
|
-
if (r.repo.lockfiles.npmLock)
|
|
365
|
-
lock.push("package-lock.json");
|
|
366
|
-
if (r.repo.lockfiles.yarnLock)
|
|
367
|
-
lock.push("yarn.lock");
|
|
368
|
-
if (r.repo.lockfiles.pnpmLock)
|
|
369
|
-
lock.push("pnpm-lock.yaml");
|
|
370
|
-
lines.push(`- Lockfiles: ${lock.length === 0 ? "none" : lock.join(", ")}`);
|
|
1090
|
+
lines.push(`- Name: ${r.repo.rootPackage?.name || "unknown"}`);
|
|
1091
|
+
lines.push(`- Version: ${r.repo.rootPackage?.version || "unknown"}`);
|
|
371
1092
|
lines.push(``);
|
|
372
|
-
if (r.
|
|
1093
|
+
if (r.packages && (r.packages.length > 1 || r.packages.length === 1 && r.repo.hasWorkspaces)) {
|
|
1094
|
+
lines.push(`## Packages Overview`);
|
|
1095
|
+
lines.push(`| Package | Path | Status | Key Findings |`);
|
|
1096
|
+
lines.push(`|---------|------|--------|--------------|`);
|
|
1097
|
+
const sortedPackages = stableSort(r.packages, (p) => p.name);
|
|
1098
|
+
for (const pkg of sortedPackages) {
|
|
1099
|
+
lines.push(packageRow(pkg));
|
|
1100
|
+
}
|
|
1101
|
+
lines.push(``);
|
|
1102
|
+
}
|
|
1103
|
+
const rootPkg = r.packages?.find((p) => p.path === r.repo.packageJsonPath);
|
|
1104
|
+
if (rootPkg?.install) {
|
|
373
1105
|
lines.push(`## bun install (dry-run)`);
|
|
374
|
-
lines.push(`- Result: ${
|
|
375
|
-
lines.push(`- Summary: ${
|
|
376
|
-
if (
|
|
1106
|
+
lines.push(`- Result: ${rootPkg.install.ok ? "ok" : "failed"}`);
|
|
1107
|
+
lines.push(`- Summary: ${rootPkg.install.summary}`);
|
|
1108
|
+
if (rootPkg.install.logs.length > 0) {
|
|
377
1109
|
lines.push(``);
|
|
378
1110
|
lines.push("```text");
|
|
379
|
-
for (const l of
|
|
1111
|
+
for (const l of rootPkg.install.logs)
|
|
380
1112
|
lines.push(l);
|
|
381
1113
|
lines.push("```");
|
|
382
1114
|
}
|
|
383
1115
|
lines.push(``);
|
|
384
1116
|
}
|
|
385
|
-
if (
|
|
1117
|
+
if (rootPkg?.test) {
|
|
386
1118
|
lines.push(`## bun test`);
|
|
387
|
-
lines.push(`- Result: ${
|
|
388
|
-
lines.push(`- Summary: ${
|
|
389
|
-
if (
|
|
1119
|
+
lines.push(`- Result: ${rootPkg.test.ok ? "ok" : "failed"}`);
|
|
1120
|
+
lines.push(`- Summary: ${rootPkg.test.summary}`);
|
|
1121
|
+
if (rootPkg.test.logs.length > 0) {
|
|
390
1122
|
lines.push(``);
|
|
391
1123
|
lines.push("```text");
|
|
392
|
-
for (const l of
|
|
1124
|
+
for (const l of rootPkg.test.logs)
|
|
393
1125
|
lines.push(l);
|
|
394
1126
|
lines.push("```");
|
|
395
1127
|
}
|
|
396
1128
|
lines.push(``);
|
|
397
1129
|
}
|
|
398
|
-
lines.push(`## Findings`);
|
|
1130
|
+
lines.push(`## Root Findings`);
|
|
399
1131
|
if (r.findings.length === 0) {
|
|
400
|
-
lines.push(`No findings
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
1132
|
+
lines.push(`No findings for root package.`);
|
|
1133
|
+
} else {
|
|
1134
|
+
const findings = stableSort(r.findings, (f) => `${f.severity}:${f.id}`);
|
|
1135
|
+
for (const f of findings) {
|
|
1136
|
+
lines.push(`### ${f.title} (${badge(f.severity)})`);
|
|
1137
|
+
lines.push(``);
|
|
1138
|
+
for (const d of f.details)
|
|
1139
|
+
lines.push(`- ${d}`);
|
|
1140
|
+
if (f.hints.length > 0) {
|
|
1141
|
+
lines.push(``);
|
|
1142
|
+
lines.push(`**Hints:**`);
|
|
1143
|
+
for (const h of f.hints)
|
|
1144
|
+
lines.push(`- ${h}`);
|
|
1145
|
+
}
|
|
1146
|
+
lines.push(``);
|
|
1147
|
+
}
|
|
404
1148
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
lines.push(
|
|
411
|
-
|
|
1149
|
+
lines.push(``);
|
|
1150
|
+
if (r.packages && r.packages.length > 0) {
|
|
1151
|
+
const sortedPackages = stableSort(r.packages, (p) => p.name);
|
|
1152
|
+
for (const pkg of sortedPackages) {
|
|
1153
|
+
lines.push(`## Package: ${pkg.name} (${badge(pkg.severity)})`);
|
|
1154
|
+
lines.push(``);
|
|
1155
|
+
lines.push(`**Path:** \`${pkg.path.replace(/\\/g, "/")}\``);
|
|
1156
|
+
lines.push(``);
|
|
1157
|
+
for (const l of pkg.summaryLines)
|
|
1158
|
+
lines.push(`- ${l}`);
|
|
1159
|
+
lines.push(``);
|
|
1160
|
+
if (pkg.install) {
|
|
1161
|
+
lines.push(`**bun install (dry-run):** ${pkg.install.ok ? "ok" : "failed"}`);
|
|
1162
|
+
if (pkg.install.logs.length > 0 && pkg.install.logs.length < 10) {
|
|
1163
|
+
lines.push(``);
|
|
1164
|
+
lines.push("```text");
|
|
1165
|
+
for (const l of pkg.install.logs)
|
|
1166
|
+
lines.push(l);
|
|
1167
|
+
lines.push("```");
|
|
1168
|
+
}
|
|
1169
|
+
lines.push(``);
|
|
1170
|
+
}
|
|
1171
|
+
if (pkg.test) {
|
|
1172
|
+
lines.push(`**bun test:** ${pkg.test.ok ? "ok" : "failed"}`);
|
|
1173
|
+
if (pkg.test.logs.length > 0 && pkg.test.logs.length < 10) {
|
|
1174
|
+
lines.push(``);
|
|
1175
|
+
lines.push("```text");
|
|
1176
|
+
for (const l of pkg.test.logs)
|
|
1177
|
+
lines.push(l);
|
|
1178
|
+
lines.push("```");
|
|
1179
|
+
}
|
|
1180
|
+
lines.push(``);
|
|
1181
|
+
}
|
|
1182
|
+
lines.push(`**Findings:**`);
|
|
1183
|
+
if (pkg.findings.length === 0) {
|
|
1184
|
+
lines.push(`No findings for this package.`);
|
|
1185
|
+
} else {
|
|
1186
|
+
const findings = stableSort(pkg.findings, (f) => `${f.severity}:${f.id}`);
|
|
1187
|
+
for (const f of findings) {
|
|
1188
|
+
lines.push(``);
|
|
1189
|
+
lines.push(`### ${f.title} (${badge(f.severity)})`);
|
|
1190
|
+
for (const d of f.details)
|
|
1191
|
+
lines.push(`- ${d}`);
|
|
1192
|
+
if (f.hints.length > 0) {
|
|
1193
|
+
lines.push(`**Hints:**`);
|
|
1194
|
+
for (const h of f.hints)
|
|
1195
|
+
lines.push(`- ${h}`);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
412
1199
|
lines.push(``);
|
|
413
|
-
lines.push(`**Hints:**`);
|
|
414
|
-
for (const h of f.hints)
|
|
415
|
-
lines.push(`- ${h}`);
|
|
416
1200
|
}
|
|
417
|
-
lines.push(``);
|
|
418
1201
|
}
|
|
419
1202
|
return lines.join(`
|
|
420
1203
|
`);
|
|
421
|
-
}
|
|
1204
|
+
}
|
|
422
1205
|
|
|
423
1206
|
// src/report_json.ts
|
|
424
|
-
|
|
1207
|
+
function renderJson(r) {
|
|
425
1208
|
return JSON.stringify(r, null, 2);
|
|
426
|
-
}
|
|
1209
|
+
}
|
|
427
1210
|
|
|
428
1211
|
// src/cli.ts
|
|
429
1212
|
var usage = () => {
|
|
@@ -431,10 +1214,22 @@ var usage = () => {
|
|
|
431
1214
|
"bun-ready",
|
|
432
1215
|
"",
|
|
433
1216
|
"Usage:",
|
|
434
|
-
" bun-ready scan <path> [--format md|json] [--out <file>] [--no-install] [--no-test] [--verbose]",
|
|
1217
|
+
" bun-ready scan <path> [--format md|json] [--out <file>] [--no-install] [--no-test] [--verbose] [--scope root|packages|all] [--fail-on green|yellow|red]",
|
|
1218
|
+
"",
|
|
1219
|
+
"Options:",
|
|
1220
|
+
" --format md|json Output format (default: md)",
|
|
1221
|
+
" --out <file> Output file path (default: bun-ready.md or bun-ready.json)",
|
|
1222
|
+
" --no-install Skip bun install --dry-run",
|
|
1223
|
+
" --no-test Skip bun test",
|
|
1224
|
+
" --verbose Show detailed output",
|
|
1225
|
+
" --scope root|packages|all Scan scope for monorepos (default: all)",
|
|
1226
|
+
" --fail-on green|yellow|red Fail policy (default: red)",
|
|
435
1227
|
"",
|
|
436
1228
|
"Exit codes:",
|
|
437
|
-
" 0
|
|
1229
|
+
" 0 green",
|
|
1230
|
+
" 2 yellow",
|
|
1231
|
+
" 3 red",
|
|
1232
|
+
" 1 invalid command"
|
|
438
1233
|
].join(`
|
|
439
1234
|
`);
|
|
440
1235
|
};
|
|
@@ -444,7 +1239,15 @@ var parseArgs = (argv) => {
|
|
|
444
1239
|
if (cmd !== "scan") {
|
|
445
1240
|
return {
|
|
446
1241
|
cmd,
|
|
447
|
-
opts: {
|
|
1242
|
+
opts: {
|
|
1243
|
+
repoPath: ".",
|
|
1244
|
+
format: "md",
|
|
1245
|
+
outFile: null,
|
|
1246
|
+
runInstall: true,
|
|
1247
|
+
runTest: true,
|
|
1248
|
+
verbose: false,
|
|
1249
|
+
scope: "all"
|
|
1250
|
+
}
|
|
448
1251
|
};
|
|
449
1252
|
}
|
|
450
1253
|
const repoPath = args[1] && !args[1].startsWith("-") ? args[1] : ".";
|
|
@@ -453,6 +1256,8 @@ var parseArgs = (argv) => {
|
|
|
453
1256
|
let runInstall = true;
|
|
454
1257
|
let runTest = true;
|
|
455
1258
|
let verbose = false;
|
|
1259
|
+
let scope = "all";
|
|
1260
|
+
let failOn;
|
|
456
1261
|
for (let i = 2;i < args.length; i++) {
|
|
457
1262
|
const a = args[i] ?? "";
|
|
458
1263
|
if (a === "--format") {
|
|
@@ -479,10 +1284,56 @@ var parseArgs = (argv) => {
|
|
|
479
1284
|
verbose = true;
|
|
480
1285
|
continue;
|
|
481
1286
|
}
|
|
1287
|
+
if (a === "--scope") {
|
|
1288
|
+
const v = args[i + 1] ?? "";
|
|
1289
|
+
if (v === "root" || v === "packages" || v === "all")
|
|
1290
|
+
scope = v;
|
|
1291
|
+
i++;
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
if (a === "--fail-on") {
|
|
1295
|
+
const v = args[i + 1] ?? "";
|
|
1296
|
+
if (v === "green" || v === "yellow" || v === "red")
|
|
1297
|
+
failOn = v;
|
|
1298
|
+
i++;
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
482
1301
|
}
|
|
483
|
-
|
|
1302
|
+
const baseOpts = {
|
|
1303
|
+
repoPath,
|
|
1304
|
+
format,
|
|
1305
|
+
outFile,
|
|
1306
|
+
runInstall,
|
|
1307
|
+
runTest,
|
|
1308
|
+
verbose,
|
|
1309
|
+
scope
|
|
1310
|
+
};
|
|
1311
|
+
if (failOn !== undefined) {
|
|
1312
|
+
baseOpts.failOn = failOn;
|
|
1313
|
+
}
|
|
1314
|
+
return {
|
|
1315
|
+
cmd,
|
|
1316
|
+
opts: baseOpts
|
|
1317
|
+
};
|
|
484
1318
|
};
|
|
485
|
-
var exitCode = (sev) => {
|
|
1319
|
+
var exitCode = (sev, failOn) => {
|
|
1320
|
+
if (!failOn) {
|
|
1321
|
+
if (sev === "green")
|
|
1322
|
+
return 0;
|
|
1323
|
+
if (sev === "yellow")
|
|
1324
|
+
return 2;
|
|
1325
|
+
return 3;
|
|
1326
|
+
}
|
|
1327
|
+
if (failOn === "green") {
|
|
1328
|
+
if (sev === "green")
|
|
1329
|
+
return 0;
|
|
1330
|
+
return 3;
|
|
1331
|
+
}
|
|
1332
|
+
if (failOn === "yellow") {
|
|
1333
|
+
if (sev === "red")
|
|
1334
|
+
return 3;
|
|
1335
|
+
return 0;
|
|
1336
|
+
}
|
|
486
1337
|
if (sev === "green")
|
|
487
1338
|
return 0;
|
|
488
1339
|
if (sev === "yellow")
|
|
@@ -496,14 +1347,42 @@ var main = async () => {
|
|
|
496
1347
|
`);
|
|
497
1348
|
process.exit(1);
|
|
498
1349
|
}
|
|
499
|
-
const
|
|
1350
|
+
const config = await mergeConfigWithOpts(null, opts);
|
|
1351
|
+
const scanOpts = {
|
|
1352
|
+
repoPath: opts.repoPath,
|
|
1353
|
+
format: opts.format,
|
|
1354
|
+
outFile: opts.outFile,
|
|
1355
|
+
runInstall: opts.runInstall,
|
|
1356
|
+
runTest: opts.runTest,
|
|
1357
|
+
verbose: opts.verbose,
|
|
1358
|
+
scope: opts.scope
|
|
1359
|
+
};
|
|
1360
|
+
if (opts.failOn !== undefined) {
|
|
1361
|
+
scanOpts.failOn = opts.failOn;
|
|
1362
|
+
}
|
|
1363
|
+
const res = await analyzeRepoOverall(scanOpts);
|
|
1364
|
+
if (res.install?.skipReason || res.test?.skipReason) {
|
|
1365
|
+
const skipWarnings = [];
|
|
1366
|
+
if (res.install?.skipReason) {
|
|
1367
|
+
skipWarnings.push(`Install check skipped: ${res.install.skipReason}`);
|
|
1368
|
+
}
|
|
1369
|
+
if (res.test?.skipReason) {
|
|
1370
|
+
skipWarnings.push(`Test run skipped: ${res.test.skipReason}`);
|
|
1371
|
+
}
|
|
1372
|
+
if (skipWarnings.length > 0) {
|
|
1373
|
+
process.stderr.write(`WARNING:
|
|
1374
|
+
${skipWarnings.map((w) => ` - ${w}`).join(`
|
|
1375
|
+
`)}
|
|
1376
|
+
`);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
500
1379
|
const out = opts.format === "json" ? renderJson(res) : renderMarkdown(res);
|
|
501
1380
|
const target = opts.outFile ?? (opts.format === "json" ? "bun-ready.json" : "bun-ready.md");
|
|
502
|
-
const resolved =
|
|
1381
|
+
const resolved = path5.resolve(process.cwd(), target);
|
|
503
1382
|
await fs3.writeFile(resolved, out, "utf8");
|
|
504
1383
|
process.stdout.write(`Wrote ${opts.format.toUpperCase()} report to ${resolved}
|
|
505
1384
|
`);
|
|
506
|
-
process.exit(exitCode(res.severity));
|
|
1385
|
+
process.exit(exitCode(res.severity, config?.failOn || opts.failOn));
|
|
507
1386
|
};
|
|
508
1387
|
main().catch((e) => {
|
|
509
1388
|
const msg = e instanceof Error ? e.message : String(e);
|