@vibgrate/cli 1.0.21 → 1.0.22
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/utils/fs.ts
|
|
2
2
|
import * as fs from "fs/promises";
|
|
3
3
|
import * as os from "os";
|
|
4
|
-
import * as
|
|
4
|
+
import * as path2 from "path";
|
|
5
5
|
|
|
6
6
|
// src/utils/semaphore.ts
|
|
7
7
|
var Semaphore = class {
|
|
@@ -32,6 +32,86 @@ var Semaphore = class {
|
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
// src/utils/glob.ts
|
|
36
|
+
import * as path from "path";
|
|
37
|
+
function compileGlobs(patterns) {
|
|
38
|
+
if (patterns.length === 0) return null;
|
|
39
|
+
const matchers = patterns.map((p) => compileOne(normalise(p)));
|
|
40
|
+
return (relPath) => {
|
|
41
|
+
const norm = normalise(relPath);
|
|
42
|
+
return matchers.some((m) => m(norm));
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function normalise(p) {
|
|
46
|
+
return p.split(path.sep).join("/").replace(/\/+$/, "");
|
|
47
|
+
}
|
|
48
|
+
function compileOne(pattern) {
|
|
49
|
+
if (!pattern.includes("/") && !hasGlobChars(pattern)) {
|
|
50
|
+
const prefix = pattern + "/";
|
|
51
|
+
return (p) => p === pattern || p.startsWith(prefix);
|
|
52
|
+
}
|
|
53
|
+
const re = globToRegex(pattern);
|
|
54
|
+
return (p) => re.test(p);
|
|
55
|
+
}
|
|
56
|
+
function hasGlobChars(s) {
|
|
57
|
+
return /[*?[\]{}]/.test(s);
|
|
58
|
+
}
|
|
59
|
+
function globToRegex(pattern) {
|
|
60
|
+
let i = 0;
|
|
61
|
+
let re = "^";
|
|
62
|
+
const len = pattern.length;
|
|
63
|
+
while (i < len) {
|
|
64
|
+
const ch = pattern[i];
|
|
65
|
+
if (ch === "*") {
|
|
66
|
+
if (pattern[i + 1] === "*") {
|
|
67
|
+
i += 2;
|
|
68
|
+
if (pattern[i] === "/") {
|
|
69
|
+
i++;
|
|
70
|
+
re += "(?:.+/)?";
|
|
71
|
+
} else {
|
|
72
|
+
re += ".*";
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
i++;
|
|
76
|
+
re += "[^/]*";
|
|
77
|
+
}
|
|
78
|
+
} else if (ch === "?") {
|
|
79
|
+
i++;
|
|
80
|
+
re += "[^/]";
|
|
81
|
+
} else if (ch === "[") {
|
|
82
|
+
const start = i;
|
|
83
|
+
i++;
|
|
84
|
+
while (i < len && pattern[i] !== "]") i++;
|
|
85
|
+
i++;
|
|
86
|
+
re += pattern.slice(start, i);
|
|
87
|
+
} else if (ch === "{") {
|
|
88
|
+
i++;
|
|
89
|
+
const alternatives = [];
|
|
90
|
+
let current = "";
|
|
91
|
+
while (i < len && pattern[i] !== "}") {
|
|
92
|
+
if (pattern[i] === ",") {
|
|
93
|
+
alternatives.push(current);
|
|
94
|
+
current = "";
|
|
95
|
+
} else {
|
|
96
|
+
current += pattern[i];
|
|
97
|
+
}
|
|
98
|
+
i++;
|
|
99
|
+
}
|
|
100
|
+
alternatives.push(current);
|
|
101
|
+
i++;
|
|
102
|
+
re += "(?:" + alternatives.map(escapeRegex).join("|") + ")";
|
|
103
|
+
} else {
|
|
104
|
+
re += escapeRegex(ch);
|
|
105
|
+
i++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
re += "$";
|
|
109
|
+
return new RegExp(re);
|
|
110
|
+
}
|
|
111
|
+
function escapeRegex(s) {
|
|
112
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
113
|
+
}
|
|
114
|
+
|
|
35
115
|
// src/utils/fs.ts
|
|
36
116
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
37
117
|
"node_modules",
|
|
@@ -60,6 +140,40 @@ var FileCache = class _FileCache {
|
|
|
60
140
|
jsonCache = /* @__PURE__ */ new Map();
|
|
61
141
|
/** pathExists keyed by absolute path */
|
|
62
142
|
existsCache = /* @__PURE__ */ new Map();
|
|
143
|
+
/** User-configured exclude predicate (compiled from glob patterns) */
|
|
144
|
+
excludePredicate = null;
|
|
145
|
+
/** Directories that were auto-skipped because they were stuck (>60s) */
|
|
146
|
+
_stuckPaths = [];
|
|
147
|
+
/** Files skipped because they exceed maxFileSizeToScan */
|
|
148
|
+
_skippedLargeFiles = [];
|
|
149
|
+
/** Maximum file size (bytes) we will read. 0 = unlimited. */
|
|
150
|
+
_maxFileSize = 0;
|
|
151
|
+
/** Root dir for relative-path computation (set by the first walkDir call) */
|
|
152
|
+
_rootDir = null;
|
|
153
|
+
/** Set exclude patterns from config (call once before the walk) */
|
|
154
|
+
setExcludePatterns(patterns) {
|
|
155
|
+
this.excludePredicate = compileGlobs(patterns);
|
|
156
|
+
}
|
|
157
|
+
/** Set the maximum file size in bytes that readTextFile / readJsonFile will process */
|
|
158
|
+
setMaxFileSize(bytes) {
|
|
159
|
+
this._maxFileSize = bytes;
|
|
160
|
+
}
|
|
161
|
+
/** Record a path that timed out or was stuck during scanning */
|
|
162
|
+
addStuckPath(relPath) {
|
|
163
|
+
this._stuckPaths.push(relPath);
|
|
164
|
+
}
|
|
165
|
+
/** Get all paths that were auto-skipped due to being stuck (dirs + scanner files) */
|
|
166
|
+
get stuckPaths() {
|
|
167
|
+
return this._stuckPaths;
|
|
168
|
+
}
|
|
169
|
+
/** @deprecated Use stuckPaths instead */
|
|
170
|
+
get stuckDirs() {
|
|
171
|
+
return this._stuckPaths;
|
|
172
|
+
}
|
|
173
|
+
/** Get files that were skipped because they exceeded maxFileSizeToScan */
|
|
174
|
+
get skippedLargeFiles() {
|
|
175
|
+
return this._skippedLargeFiles;
|
|
176
|
+
}
|
|
63
177
|
// ── Directory walking ──
|
|
64
178
|
/**
|
|
65
179
|
* Walk the directory tree from `rootDir` once, skipping SKIP_DIRS plus
|
|
@@ -70,6 +184,7 @@ var FileCache = class _FileCache {
|
|
|
70
184
|
* SKIP_EXTENSIONS) do so on the returned entries — no separate walk.
|
|
71
185
|
*/
|
|
72
186
|
walkDir(rootDir, onProgress) {
|
|
187
|
+
this._rootDir = rootDir;
|
|
73
188
|
const cached = this.walkCache.get(rootDir);
|
|
74
189
|
if (cached) return cached;
|
|
75
190
|
const promise = this._doWalk(rootDir, onProgress);
|
|
@@ -86,18 +201,34 @@ var FileCache = class _FileCache {
|
|
|
86
201
|
let lastReported = 0;
|
|
87
202
|
const REPORT_INTERVAL = 50;
|
|
88
203
|
const sem = new Semaphore(maxConcurrentReads);
|
|
204
|
+
const STUCK_TIMEOUT_MS = 6e4;
|
|
89
205
|
const extraSkip = _FileCache.EXTRA_SKIP;
|
|
206
|
+
const isExcluded = this.excludePredicate;
|
|
207
|
+
const stuckDirs = this._stuckPaths;
|
|
90
208
|
async function walk(dir) {
|
|
209
|
+
const relDir = path2.relative(rootDir, dir);
|
|
91
210
|
let entries;
|
|
92
211
|
try {
|
|
93
|
-
|
|
212
|
+
const readPromise = fs.readdir(dir, { withFileTypes: true });
|
|
213
|
+
const result = await Promise.race([
|
|
214
|
+
readPromise.then((e) => ({ ok: true, entries: e })),
|
|
215
|
+
new Promise(
|
|
216
|
+
(resolve7) => setTimeout(() => resolve7({ ok: false }), STUCK_TIMEOUT_MS)
|
|
217
|
+
)
|
|
218
|
+
]);
|
|
219
|
+
if (!result.ok) {
|
|
220
|
+
stuckDirs.push(relDir || dir);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
entries = result.entries;
|
|
94
224
|
} catch {
|
|
95
225
|
return;
|
|
96
226
|
}
|
|
97
227
|
const subWalks = [];
|
|
98
228
|
for (const e of entries) {
|
|
99
|
-
const absPath =
|
|
100
|
-
const relPath =
|
|
229
|
+
const absPath = path2.join(dir, e.name);
|
|
230
|
+
const relPath = path2.relative(rootDir, absPath);
|
|
231
|
+
if (isExcluded && isExcluded(relPath)) continue;
|
|
101
232
|
if (e.isDirectory()) {
|
|
102
233
|
if (SKIP_DIRS.has(e.name) || extraSkip.has(e.name)) continue;
|
|
103
234
|
results.push({ absPath, relPath, name: e.name, isFile: false, isDirectory: true });
|
|
@@ -107,7 +238,7 @@ var FileCache = class _FileCache {
|
|
|
107
238
|
foundCount++;
|
|
108
239
|
if (onProgress && foundCount - lastReported >= REPORT_INTERVAL) {
|
|
109
240
|
lastReported = foundCount;
|
|
110
|
-
onProgress(foundCount);
|
|
241
|
+
onProgress(foundCount, relPath);
|
|
111
242
|
}
|
|
112
243
|
}
|
|
113
244
|
}
|
|
@@ -115,7 +246,7 @@ var FileCache = class _FileCache {
|
|
|
115
246
|
}
|
|
116
247
|
await sem.run(() => walk(rootDir));
|
|
117
248
|
if (onProgress && foundCount !== lastReported) {
|
|
118
|
-
onProgress(foundCount);
|
|
249
|
+
onProgress(foundCount, "");
|
|
119
250
|
}
|
|
120
251
|
return results;
|
|
121
252
|
}
|
|
@@ -141,17 +272,36 @@ var FileCache = class _FileCache {
|
|
|
141
272
|
* Read a text file. Files ≤ 1 MB are cached so subsequent calls from
|
|
142
273
|
* different scanners return the same string. Files > 1 MB (lockfiles,
|
|
143
274
|
* large generated files) are read directly and never retained.
|
|
275
|
+
*
|
|
276
|
+
* If maxFileSizeToScan is set and the file exceeds it, the file is
|
|
277
|
+
* recorded as skipped and an empty string is returned.
|
|
144
278
|
*/
|
|
145
279
|
readTextFile(filePath) {
|
|
146
|
-
const abs =
|
|
280
|
+
const abs = path2.resolve(filePath);
|
|
147
281
|
const cached = this.textCache.get(abs);
|
|
148
282
|
if (cached) return cached;
|
|
149
|
-
const
|
|
283
|
+
const maxSize = this._maxFileSize;
|
|
284
|
+
const skippedLarge = this._skippedLargeFiles;
|
|
285
|
+
const rootDir = this._rootDir;
|
|
286
|
+
const promise = (async () => {
|
|
287
|
+
if (maxSize > 0) {
|
|
288
|
+
try {
|
|
289
|
+
const stat4 = await fs.stat(abs);
|
|
290
|
+
if (stat4.size > maxSize) {
|
|
291
|
+
const rel = rootDir ? path2.relative(rootDir, abs) : abs;
|
|
292
|
+
skippedLarge.push(rel);
|
|
293
|
+
this.textCache.delete(abs);
|
|
294
|
+
return "";
|
|
295
|
+
}
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const content = await fs.readFile(abs, "utf8");
|
|
150
300
|
if (content.length > TEXT_CACHE_MAX_BYTES) {
|
|
151
301
|
this.textCache.delete(abs);
|
|
152
302
|
}
|
|
153
303
|
return content;
|
|
154
|
-
});
|
|
304
|
+
})();
|
|
155
305
|
this.textCache.set(abs, promise);
|
|
156
306
|
return promise;
|
|
157
307
|
}
|
|
@@ -160,7 +310,7 @@ var FileCache = class _FileCache {
|
|
|
160
310
|
* text is evicted immediately so we never hold both representations.
|
|
161
311
|
*/
|
|
162
312
|
readJsonFile(filePath) {
|
|
163
|
-
const abs =
|
|
313
|
+
const abs = path2.resolve(filePath);
|
|
164
314
|
const cached = this.jsonCache.get(abs);
|
|
165
315
|
if (cached) return cached;
|
|
166
316
|
const promise = this.readTextFile(abs).then((txt) => {
|
|
@@ -172,7 +322,7 @@ var FileCache = class _FileCache {
|
|
|
172
322
|
}
|
|
173
323
|
// ── Existence checks ──
|
|
174
324
|
pathExists(p) {
|
|
175
|
-
const abs =
|
|
325
|
+
const abs = path2.resolve(p);
|
|
176
326
|
const cached = this.existsCache.get(abs);
|
|
177
327
|
if (cached) return cached;
|
|
178
328
|
const promise = fs.access(abs).then(() => true, () => false);
|
|
@@ -196,13 +346,14 @@ var FileCache = class _FileCache {
|
|
|
196
346
|
return this.jsonCache.size;
|
|
197
347
|
}
|
|
198
348
|
};
|
|
199
|
-
async function quickTreeCount(rootDir) {
|
|
349
|
+
async function quickTreeCount(rootDir, excludePatterns) {
|
|
200
350
|
let totalFiles = 0;
|
|
201
351
|
let totalDirs = 0;
|
|
202
352
|
const cores = typeof os.availableParallelism === "function" ? os.availableParallelism() : os.cpus().length || 4;
|
|
203
353
|
const maxConcurrent = Math.max(8, Math.min(128, cores * 8));
|
|
204
354
|
const sem = new Semaphore(maxConcurrent);
|
|
205
355
|
const extraSkip = /* @__PURE__ */ new Set([".nuxt", ".output", ".svelte-kit"]);
|
|
356
|
+
const isExcluded = excludePatterns ? compileGlobs(excludePatterns) : null;
|
|
206
357
|
async function count(dir) {
|
|
207
358
|
let entries;
|
|
208
359
|
try {
|
|
@@ -212,10 +363,12 @@ async function quickTreeCount(rootDir) {
|
|
|
212
363
|
}
|
|
213
364
|
const subs = [];
|
|
214
365
|
for (const e of entries) {
|
|
366
|
+
const relPath = path2.relative(rootDir, path2.join(dir, e.name));
|
|
367
|
+
if (isExcluded && isExcluded(relPath)) continue;
|
|
215
368
|
if (e.isDirectory()) {
|
|
216
369
|
if (SKIP_DIRS.has(e.name) || extraSkip.has(e.name)) continue;
|
|
217
370
|
totalDirs++;
|
|
218
|
-
subs.push(sem.run(() => count(
|
|
371
|
+
subs.push(sem.run(() => count(path2.join(dir, e.name))));
|
|
219
372
|
} else if (e.isFile()) {
|
|
220
373
|
totalFiles++;
|
|
221
374
|
}
|
|
@@ -241,9 +394,9 @@ async function findFiles(rootDir, predicate) {
|
|
|
241
394
|
for (const e of entries) {
|
|
242
395
|
if (e.isDirectory()) {
|
|
243
396
|
if (SKIP_DIRS.has(e.name)) continue;
|
|
244
|
-
subDirectoryWalks.push(readDirSemaphore.run(() => walk(
|
|
397
|
+
subDirectoryWalks.push(readDirSemaphore.run(() => walk(path2.join(dir, e.name))));
|
|
245
398
|
} else if (e.isFile() && predicate(e.name)) {
|
|
246
|
-
results.push(
|
|
399
|
+
results.push(path2.join(dir, e.name));
|
|
247
400
|
}
|
|
248
401
|
}
|
|
249
402
|
await Promise.all(subDirectoryWalks);
|
|
@@ -279,11 +432,11 @@ async function ensureDir(dir) {
|
|
|
279
432
|
await fs.mkdir(dir, { recursive: true });
|
|
280
433
|
}
|
|
281
434
|
async function writeJsonFile(filePath, data) {
|
|
282
|
-
await ensureDir(
|
|
435
|
+
await ensureDir(path2.dirname(filePath));
|
|
283
436
|
await fs.writeFile(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
284
437
|
}
|
|
285
438
|
async function writeTextFile(filePath, content) {
|
|
286
|
-
await ensureDir(
|
|
439
|
+
await ensureDir(path2.dirname(filePath));
|
|
287
440
|
await fs.writeFile(filePath, content, "utf8");
|
|
288
441
|
}
|
|
289
442
|
|
|
@@ -1217,7 +1370,7 @@ function toSarifResult(finding) {
|
|
|
1217
1370
|
|
|
1218
1371
|
// src/commands/dsn.ts
|
|
1219
1372
|
import * as crypto2 from "crypto";
|
|
1220
|
-
import * as
|
|
1373
|
+
import * as path3 from "path";
|
|
1221
1374
|
import { Command } from "commander";
|
|
1222
1375
|
import chalk2 from "chalk";
|
|
1223
1376
|
var REGION_HOSTS = {
|
|
@@ -1262,7 +1415,7 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
1262
1415
|
console.log(chalk2.dim("Set this as VIBGRATE_DSN in your CI environment."));
|
|
1263
1416
|
console.log(chalk2.dim("The secret must be registered on your Vibgrate ingest API."));
|
|
1264
1417
|
if (opts.write) {
|
|
1265
|
-
const writePath =
|
|
1418
|
+
const writePath = path3.resolve(opts.write);
|
|
1266
1419
|
await writeTextFile(writePath, dsn + "\n");
|
|
1267
1420
|
console.log("");
|
|
1268
1421
|
console.log(chalk2.green("\u2714") + ` DSN written to ${opts.write}`);
|
|
@@ -1272,7 +1425,7 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
1272
1425
|
|
|
1273
1426
|
// src/commands/push.ts
|
|
1274
1427
|
import * as crypto3 from "crypto";
|
|
1275
|
-
import * as
|
|
1428
|
+
import * as path4 from "path";
|
|
1276
1429
|
import { Command as Command2 } from "commander";
|
|
1277
1430
|
import chalk3 from "chalk";
|
|
1278
1431
|
function parseDsn(dsn) {
|
|
@@ -1301,7 +1454,7 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
|
|
|
1301
1454
|
if (opts.strict) process.exit(1);
|
|
1302
1455
|
return;
|
|
1303
1456
|
}
|
|
1304
|
-
const filePath =
|
|
1457
|
+
const filePath = path4.resolve(opts.file);
|
|
1305
1458
|
if (!await pathExists(filePath)) {
|
|
1306
1459
|
console.error(chalk3.red(`Scan artifact not found: ${filePath}`));
|
|
1307
1460
|
console.error(chalk3.dim('Run "vibgrate scan" first.'));
|
|
@@ -1346,14 +1499,31 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
|
|
|
1346
1499
|
});
|
|
1347
1500
|
|
|
1348
1501
|
// src/commands/scan.ts
|
|
1349
|
-
import * as
|
|
1502
|
+
import * as path17 from "path";
|
|
1350
1503
|
import { Command as Command3 } from "commander";
|
|
1351
1504
|
import chalk5 from "chalk";
|
|
1352
1505
|
|
|
1353
1506
|
// src/scanners/node-scanner.ts
|
|
1354
|
-
import * as
|
|
1507
|
+
import * as path5 from "path";
|
|
1355
1508
|
import * as semver2 from "semver";
|
|
1356
1509
|
|
|
1510
|
+
// src/utils/timeout.ts
|
|
1511
|
+
async function withTimeout(promise, ms) {
|
|
1512
|
+
let timer;
|
|
1513
|
+
const timeout = new Promise((resolve7) => {
|
|
1514
|
+
timer = setTimeout(() => resolve7({ ok: false }), ms);
|
|
1515
|
+
});
|
|
1516
|
+
try {
|
|
1517
|
+
const result = await Promise.race([
|
|
1518
|
+
promise.then((value) => ({ ok: true, value })),
|
|
1519
|
+
timeout
|
|
1520
|
+
]);
|
|
1521
|
+
return result;
|
|
1522
|
+
} finally {
|
|
1523
|
+
clearTimeout(timer);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1357
1527
|
// src/scanners/npm-cache.ts
|
|
1358
1528
|
import { spawn } from "child_process";
|
|
1359
1529
|
import * as semver from "semver";
|
|
@@ -1528,10 +1698,20 @@ var KNOWN_FRAMEWORKS = {
|
|
|
1528
1698
|
async function scanNodeProjects(rootDir, npmCache, cache) {
|
|
1529
1699
|
const packageJsonFiles = cache ? await cache.findPackageJsonFiles(rootDir) : await findPackageJsonFiles(rootDir);
|
|
1530
1700
|
const results = [];
|
|
1701
|
+
const STUCK_TIMEOUT_MS = 6e4;
|
|
1531
1702
|
for (const pjPath of packageJsonFiles) {
|
|
1532
1703
|
try {
|
|
1533
|
-
const
|
|
1534
|
-
|
|
1704
|
+
const scanPromise = scanOnePackageJson(pjPath, rootDir, npmCache, cache);
|
|
1705
|
+
const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
|
|
1706
|
+
if (result.ok) {
|
|
1707
|
+
results.push(result.value);
|
|
1708
|
+
} else {
|
|
1709
|
+
const relPath = path5.relative(rootDir, path5.dirname(pjPath));
|
|
1710
|
+
if (cache) {
|
|
1711
|
+
cache.addStuckPath(relPath || ".");
|
|
1712
|
+
}
|
|
1713
|
+
console.error(`Timeout scanning ${pjPath} (>${STUCK_TIMEOUT_MS / 1e3}s) \u2014 skipped`);
|
|
1714
|
+
}
|
|
1535
1715
|
} catch (e) {
|
|
1536
1716
|
const msg = e instanceof Error ? e.message : String(e);
|
|
1537
1717
|
console.error(`Error scanning ${pjPath}: ${msg}`);
|
|
@@ -1541,8 +1721,8 @@ async function scanNodeProjects(rootDir, npmCache, cache) {
|
|
|
1541
1721
|
}
|
|
1542
1722
|
async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
|
|
1543
1723
|
const pj = cache ? await cache.readJsonFile(packageJsonPath) : await readJsonFile(packageJsonPath);
|
|
1544
|
-
const absProjectPath =
|
|
1545
|
-
const projectPath =
|
|
1724
|
+
const absProjectPath = path5.dirname(packageJsonPath);
|
|
1725
|
+
const projectPath = path5.relative(rootDir, absProjectPath) || ".";
|
|
1546
1726
|
const nodeEngine = pj.engines?.node ?? void 0;
|
|
1547
1727
|
let runtimeLatest;
|
|
1548
1728
|
let runtimeMajorsBehind;
|
|
@@ -1624,7 +1804,7 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
|
|
|
1624
1804
|
return {
|
|
1625
1805
|
type: "node",
|
|
1626
1806
|
path: projectPath,
|
|
1627
|
-
name: pj.name ??
|
|
1807
|
+
name: pj.name ?? path5.basename(absProjectPath),
|
|
1628
1808
|
runtime: nodeEngine,
|
|
1629
1809
|
runtimeLatest,
|
|
1630
1810
|
runtimeMajorsBehind,
|
|
@@ -1635,7 +1815,7 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache, cache) {
|
|
|
1635
1815
|
}
|
|
1636
1816
|
|
|
1637
1817
|
// src/scanners/dotnet-scanner.ts
|
|
1638
|
-
import * as
|
|
1818
|
+
import * as path6 from "path";
|
|
1639
1819
|
import { XMLParser } from "fast-xml-parser";
|
|
1640
1820
|
var parser = new XMLParser({
|
|
1641
1821
|
ignoreAttributes: false,
|
|
@@ -1836,7 +2016,7 @@ function parseCsproj(xml, filePath) {
|
|
|
1836
2016
|
const parsed = parser.parse(xml);
|
|
1837
2017
|
const project = parsed?.Project;
|
|
1838
2018
|
if (!project) {
|
|
1839
|
-
return { targetFrameworks: [], packageReferences: [], projectName:
|
|
2019
|
+
return { targetFrameworks: [], packageReferences: [], projectName: path6.basename(filePath, ".csproj") };
|
|
1840
2020
|
}
|
|
1841
2021
|
const propertyGroups = Array.isArray(project.PropertyGroup) ? project.PropertyGroup : project.PropertyGroup ? [project.PropertyGroup] : [];
|
|
1842
2022
|
const targetFrameworks = [];
|
|
@@ -1864,7 +2044,7 @@ function parseCsproj(xml, filePath) {
|
|
|
1864
2044
|
return {
|
|
1865
2045
|
targetFrameworks: [...new Set(targetFrameworks)],
|
|
1866
2046
|
packageReferences,
|
|
1867
|
-
projectName:
|
|
2047
|
+
projectName: path6.basename(filePath, ".csproj")
|
|
1868
2048
|
};
|
|
1869
2049
|
}
|
|
1870
2050
|
async function scanDotnetProjects(rootDir, cache) {
|
|
@@ -1874,12 +2054,12 @@ async function scanDotnetProjects(rootDir, cache) {
|
|
|
1874
2054
|
for (const slnPath of slnFiles) {
|
|
1875
2055
|
try {
|
|
1876
2056
|
const slnContent = cache ? await cache.readTextFile(slnPath) : await readTextFile(slnPath);
|
|
1877
|
-
const slnDir =
|
|
2057
|
+
const slnDir = path6.dirname(slnPath);
|
|
1878
2058
|
const projectRegex = /Project\("[^"]*"\)\s*=\s*"[^"]*",\s*"([^"]+\.csproj)"/g;
|
|
1879
2059
|
let match;
|
|
1880
2060
|
while ((match = projectRegex.exec(slnContent)) !== null) {
|
|
1881
2061
|
if (match[1]) {
|
|
1882
|
-
const csprojPath =
|
|
2062
|
+
const csprojPath = path6.resolve(slnDir, match[1].replace(/\\/g, "/"));
|
|
1883
2063
|
slnCsprojPaths.add(csprojPath);
|
|
1884
2064
|
}
|
|
1885
2065
|
}
|
|
@@ -1888,10 +2068,20 @@ async function scanDotnetProjects(rootDir, cache) {
|
|
|
1888
2068
|
}
|
|
1889
2069
|
const allCsprojFiles = /* @__PURE__ */ new Set([...csprojFiles, ...slnCsprojPaths]);
|
|
1890
2070
|
const results = [];
|
|
2071
|
+
const STUCK_TIMEOUT_MS = 6e4;
|
|
1891
2072
|
for (const csprojPath of allCsprojFiles) {
|
|
1892
2073
|
try {
|
|
1893
|
-
const
|
|
1894
|
-
|
|
2074
|
+
const scanPromise = scanOneCsproj(csprojPath, rootDir, cache);
|
|
2075
|
+
const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
|
|
2076
|
+
if (result.ok) {
|
|
2077
|
+
results.push(result.value);
|
|
2078
|
+
} else {
|
|
2079
|
+
const relPath = path6.relative(rootDir, path6.dirname(csprojPath));
|
|
2080
|
+
if (cache) {
|
|
2081
|
+
cache.addStuckPath(relPath || ".");
|
|
2082
|
+
}
|
|
2083
|
+
console.error(`Timeout scanning ${csprojPath} (>${STUCK_TIMEOUT_MS / 1e3}s) \u2014 skipped`);
|
|
2084
|
+
}
|
|
1895
2085
|
} catch (e) {
|
|
1896
2086
|
const msg = e instanceof Error ? e.message : String(e);
|
|
1897
2087
|
console.error(`Error scanning ${csprojPath}: ${msg}`);
|
|
@@ -1935,7 +2125,7 @@ async function scanOneCsproj(csprojPath, rootDir, cache) {
|
|
|
1935
2125
|
const buckets = { current: 0, oneBehind: 0, twoPlusBehind: 0, unknown: dependencies.length };
|
|
1936
2126
|
return {
|
|
1937
2127
|
type: "dotnet",
|
|
1938
|
-
path:
|
|
2128
|
+
path: path6.relative(rootDir, path6.dirname(csprojPath)) || ".",
|
|
1939
2129
|
name: data.projectName,
|
|
1940
2130
|
targetFramework,
|
|
1941
2131
|
runtime: primaryTfm,
|
|
@@ -1948,15 +2138,17 @@ async function scanOneCsproj(csprojPath, rootDir, cache) {
|
|
|
1948
2138
|
}
|
|
1949
2139
|
|
|
1950
2140
|
// src/config.ts
|
|
1951
|
-
import * as
|
|
2141
|
+
import * as path7 from "path";
|
|
1952
2142
|
import * as fs2 from "fs/promises";
|
|
1953
2143
|
var CONFIG_FILES = [
|
|
1954
2144
|
"vibgrate.config.ts",
|
|
1955
2145
|
"vibgrate.config.js",
|
|
1956
2146
|
"vibgrate.config.json"
|
|
1957
2147
|
];
|
|
2148
|
+
var DEFAULT_MAX_FILE_SIZE = 5242880;
|
|
1958
2149
|
var DEFAULT_CONFIG = {
|
|
1959
2150
|
exclude: [],
|
|
2151
|
+
maxFileSizeToScan: DEFAULT_MAX_FILE_SIZE,
|
|
1960
2152
|
thresholds: {
|
|
1961
2153
|
failOnError: {
|
|
1962
2154
|
eolDays: 180,
|
|
@@ -1970,28 +2162,44 @@ var DEFAULT_CONFIG = {
|
|
|
1970
2162
|
}
|
|
1971
2163
|
};
|
|
1972
2164
|
async function loadConfig(rootDir) {
|
|
2165
|
+
let config = DEFAULT_CONFIG;
|
|
1973
2166
|
for (const file of CONFIG_FILES) {
|
|
1974
|
-
const configPath =
|
|
2167
|
+
const configPath = path7.join(rootDir, file);
|
|
1975
2168
|
if (await pathExists(configPath)) {
|
|
1976
2169
|
if (file.endsWith(".json")) {
|
|
1977
2170
|
const txt = await readTextFile(configPath);
|
|
1978
|
-
|
|
2171
|
+
config = { ...DEFAULT_CONFIG, ...JSON.parse(txt) };
|
|
2172
|
+
break;
|
|
1979
2173
|
}
|
|
1980
2174
|
try {
|
|
1981
2175
|
const mod = await import(configPath);
|
|
1982
|
-
|
|
2176
|
+
config = { ...DEFAULT_CONFIG, ...mod.default ?? mod };
|
|
2177
|
+
break;
|
|
1983
2178
|
} catch {
|
|
1984
2179
|
}
|
|
1985
2180
|
}
|
|
1986
2181
|
}
|
|
1987
|
-
|
|
2182
|
+
const sidecarPath = path7.join(rootDir, ".vibgrate", "auto-excludes.json");
|
|
2183
|
+
if (await pathExists(sidecarPath)) {
|
|
2184
|
+
try {
|
|
2185
|
+
const txt = await readTextFile(sidecarPath);
|
|
2186
|
+
const autoExcludes = JSON.parse(txt);
|
|
2187
|
+
if (Array.isArray(autoExcludes) && autoExcludes.length > 0) {
|
|
2188
|
+
const existing = config.exclude ?? [];
|
|
2189
|
+
config = { ...config, exclude: [.../* @__PURE__ */ new Set([...existing, ...autoExcludes])] };
|
|
2190
|
+
}
|
|
2191
|
+
} catch {
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
return config;
|
|
1988
2195
|
}
|
|
1989
2196
|
async function writeDefaultConfig(rootDir) {
|
|
1990
|
-
const configPath =
|
|
2197
|
+
const configPath = path7.join(rootDir, "vibgrate.config.ts");
|
|
1991
2198
|
const content = `import type { VibgrateConfig } from '@vibgrate/cli';
|
|
1992
2199
|
|
|
1993
2200
|
const config: VibgrateConfig = {
|
|
1994
2201
|
// exclude: ['legacy/**'],
|
|
2202
|
+
// maxFileSizeToScan: 5_242_880, // 5 MB (default)
|
|
1995
2203
|
thresholds: {
|
|
1996
2204
|
failOnError: {
|
|
1997
2205
|
eolDays: 180,
|
|
@@ -2010,9 +2218,44 @@ export default config;
|
|
|
2010
2218
|
await fs2.writeFile(configPath, content, "utf8");
|
|
2011
2219
|
return configPath;
|
|
2012
2220
|
}
|
|
2221
|
+
async function appendExcludePatterns(rootDir, newPatterns) {
|
|
2222
|
+
if (newPatterns.length === 0) return false;
|
|
2223
|
+
const jsonPath = path7.join(rootDir, "vibgrate.config.json");
|
|
2224
|
+
if (await pathExists(jsonPath)) {
|
|
2225
|
+
try {
|
|
2226
|
+
const txt = await readTextFile(jsonPath);
|
|
2227
|
+
const cfg = JSON.parse(txt);
|
|
2228
|
+
const existing2 = Array.isArray(cfg.exclude) ? cfg.exclude : [];
|
|
2229
|
+
const merged2 = [.../* @__PURE__ */ new Set([...existing2, ...newPatterns])];
|
|
2230
|
+
cfg.exclude = merged2;
|
|
2231
|
+
await fs2.writeFile(jsonPath, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
2232
|
+
return true;
|
|
2233
|
+
} catch {
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
const vibgrateDir = path7.join(rootDir, ".vibgrate");
|
|
2237
|
+
const sidecarPath = path7.join(vibgrateDir, "auto-excludes.json");
|
|
2238
|
+
let existing = [];
|
|
2239
|
+
if (await pathExists(sidecarPath)) {
|
|
2240
|
+
try {
|
|
2241
|
+
const txt = await readTextFile(sidecarPath);
|
|
2242
|
+
const parsed = JSON.parse(txt);
|
|
2243
|
+
if (Array.isArray(parsed)) existing = parsed;
|
|
2244
|
+
} catch {
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
const merged = [.../* @__PURE__ */ new Set([...existing, ...newPatterns])];
|
|
2248
|
+
try {
|
|
2249
|
+
await fs2.mkdir(vibgrateDir, { recursive: true });
|
|
2250
|
+
await fs2.writeFile(sidecarPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
2251
|
+
return true;
|
|
2252
|
+
} catch {
|
|
2253
|
+
return false;
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2013
2256
|
|
|
2014
2257
|
// src/utils/vcs.ts
|
|
2015
|
-
import * as
|
|
2258
|
+
import * as path8 from "path";
|
|
2016
2259
|
import * as fs3 from "fs/promises";
|
|
2017
2260
|
async function detectVcs(rootDir) {
|
|
2018
2261
|
try {
|
|
@@ -2026,7 +2269,7 @@ async function detectGit(rootDir) {
|
|
|
2026
2269
|
if (!gitDir) {
|
|
2027
2270
|
return { type: "unknown" };
|
|
2028
2271
|
}
|
|
2029
|
-
const headPath =
|
|
2272
|
+
const headPath = path8.join(gitDir, "HEAD");
|
|
2030
2273
|
let headContent;
|
|
2031
2274
|
try {
|
|
2032
2275
|
headContent = (await fs3.readFile(headPath, "utf8")).trim();
|
|
@@ -2050,30 +2293,30 @@ async function detectGit(rootDir) {
|
|
|
2050
2293
|
};
|
|
2051
2294
|
}
|
|
2052
2295
|
async function findGitDir(startDir) {
|
|
2053
|
-
let dir =
|
|
2054
|
-
const root =
|
|
2296
|
+
let dir = path8.resolve(startDir);
|
|
2297
|
+
const root = path8.parse(dir).root;
|
|
2055
2298
|
while (dir !== root) {
|
|
2056
|
-
const gitPath =
|
|
2299
|
+
const gitPath = path8.join(dir, ".git");
|
|
2057
2300
|
try {
|
|
2058
|
-
const
|
|
2059
|
-
if (
|
|
2301
|
+
const stat4 = await fs3.stat(gitPath);
|
|
2302
|
+
if (stat4.isDirectory()) {
|
|
2060
2303
|
return gitPath;
|
|
2061
2304
|
}
|
|
2062
|
-
if (
|
|
2305
|
+
if (stat4.isFile()) {
|
|
2063
2306
|
const content = (await fs3.readFile(gitPath, "utf8")).trim();
|
|
2064
2307
|
if (content.startsWith("gitdir: ")) {
|
|
2065
|
-
const resolved =
|
|
2308
|
+
const resolved = path8.resolve(dir, content.slice(8));
|
|
2066
2309
|
return resolved;
|
|
2067
2310
|
}
|
|
2068
2311
|
}
|
|
2069
2312
|
} catch {
|
|
2070
2313
|
}
|
|
2071
|
-
dir =
|
|
2314
|
+
dir = path8.dirname(dir);
|
|
2072
2315
|
}
|
|
2073
2316
|
return null;
|
|
2074
2317
|
}
|
|
2075
2318
|
async function resolveRef(gitDir, refPath) {
|
|
2076
|
-
const loosePath =
|
|
2319
|
+
const loosePath = path8.join(gitDir, refPath);
|
|
2077
2320
|
try {
|
|
2078
2321
|
const sha = (await fs3.readFile(loosePath, "utf8")).trim();
|
|
2079
2322
|
if (/^[0-9a-f]{40}$/i.test(sha)) {
|
|
@@ -2081,7 +2324,7 @@ async function resolveRef(gitDir, refPath) {
|
|
|
2081
2324
|
}
|
|
2082
2325
|
} catch {
|
|
2083
2326
|
}
|
|
2084
|
-
const packedPath =
|
|
2327
|
+
const packedPath = path8.join(gitDir, "packed-refs");
|
|
2085
2328
|
try {
|
|
2086
2329
|
const packed = await fs3.readFile(packedPath, "utf8");
|
|
2087
2330
|
for (const line of packed.split("\n")) {
|
|
@@ -2123,6 +2366,10 @@ var ScanProgress = class {
|
|
|
2123
2366
|
startTime = Date.now();
|
|
2124
2367
|
isTTY;
|
|
2125
2368
|
rootDir = "";
|
|
2369
|
+
/** Last rendered frame content (strip to compare for dirty-checking) */
|
|
2370
|
+
lastFrame = "";
|
|
2371
|
+
/** Whether we've hidden the cursor */
|
|
2372
|
+
cursorHidden = false;
|
|
2126
2373
|
/** Estimated total scan duration in ms (from history or live calculation) */
|
|
2127
2374
|
estimatedTotalMs = null;
|
|
2128
2375
|
/** Per-step estimated durations from history */
|
|
@@ -2134,6 +2381,23 @@ var ScanProgress = class {
|
|
|
2134
2381
|
constructor(rootDir) {
|
|
2135
2382
|
this.isTTY = process.stderr.isTTY ?? false;
|
|
2136
2383
|
this.rootDir = rootDir;
|
|
2384
|
+
if (this.isTTY) {
|
|
2385
|
+
const restore = () => {
|
|
2386
|
+
if (this.cursorHidden) {
|
|
2387
|
+
process.stderr.write("\x1B[?25h");
|
|
2388
|
+
this.cursorHidden = false;
|
|
2389
|
+
}
|
|
2390
|
+
};
|
|
2391
|
+
process.on("exit", restore);
|
|
2392
|
+
process.on("SIGINT", () => {
|
|
2393
|
+
restore();
|
|
2394
|
+
process.exit(130);
|
|
2395
|
+
});
|
|
2396
|
+
process.on("SIGTERM", () => {
|
|
2397
|
+
restore();
|
|
2398
|
+
process.exit(143);
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2137
2401
|
}
|
|
2138
2402
|
/** Set the estimated total duration from scan history */
|
|
2139
2403
|
setEstimatedTotal(estimatedMs) {
|
|
@@ -2192,11 +2456,12 @@ var ScanProgress = class {
|
|
|
2192
2456
|
this.render();
|
|
2193
2457
|
}
|
|
2194
2458
|
/** Update sub-step progress for the active step (files processed, etc.) */
|
|
2195
|
-
updateStepProgress(id, current, total) {
|
|
2459
|
+
updateStepProgress(id, current, total, label) {
|
|
2196
2460
|
const step = this.steps.find((s) => s.id === id);
|
|
2197
2461
|
if (step) {
|
|
2198
2462
|
step.subProgress = current;
|
|
2199
2463
|
if (total !== void 0) step.subTotal = total;
|
|
2464
|
+
if (label !== void 0) step.subLabel = label;
|
|
2200
2465
|
}
|
|
2201
2466
|
this.render();
|
|
2202
2467
|
}
|
|
@@ -2231,7 +2496,18 @@ var ScanProgress = class {
|
|
|
2231
2496
|
this.timer = null;
|
|
2232
2497
|
}
|
|
2233
2498
|
if (this.isTTY) {
|
|
2234
|
-
|
|
2499
|
+
let buf = "";
|
|
2500
|
+
if (this.lastLineCount > 0) {
|
|
2501
|
+
buf += `\x1B[${this.lastLineCount}A`;
|
|
2502
|
+
for (let i = 0; i < this.lastLineCount; i++) {
|
|
2503
|
+
buf += "\x1B[2K\n";
|
|
2504
|
+
}
|
|
2505
|
+
buf += `\x1B[${this.lastLineCount}A`;
|
|
2506
|
+
}
|
|
2507
|
+
buf += "\x1B[?25h";
|
|
2508
|
+
if (buf) process.stderr.write(buf);
|
|
2509
|
+
this.cursorHidden = false;
|
|
2510
|
+
this.lastLineCount = 0;
|
|
2235
2511
|
}
|
|
2236
2512
|
const elapsed = this.formatElapsed(Date.now() - this.startTime);
|
|
2237
2513
|
const doneCount = this.steps.filter((s) => s.status === "done").length;
|
|
@@ -2243,26 +2519,22 @@ var ScanProgress = class {
|
|
|
2243
2519
|
}
|
|
2244
2520
|
// ── Internal rendering ──
|
|
2245
2521
|
startSpinner() {
|
|
2522
|
+
if (!this.cursorHidden) {
|
|
2523
|
+
process.stderr.write("\x1B[?25l");
|
|
2524
|
+
this.cursorHidden = true;
|
|
2525
|
+
}
|
|
2246
2526
|
this.timer = setInterval(() => {
|
|
2247
2527
|
this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
2248
2528
|
this.render();
|
|
2249
|
-
},
|
|
2529
|
+
}, 120);
|
|
2250
2530
|
}
|
|
2251
2531
|
clearLines() {
|
|
2252
|
-
if (this.lastLineCount > 0) {
|
|
2253
|
-
process.stderr.write(`\x1B[${this.lastLineCount}A`);
|
|
2254
|
-
for (let i = 0; i < this.lastLineCount; i++) {
|
|
2255
|
-
process.stderr.write("\x1B[2K\n");
|
|
2256
|
-
}
|
|
2257
|
-
process.stderr.write(`\x1B[${this.lastLineCount}A`);
|
|
2258
|
-
}
|
|
2259
2532
|
}
|
|
2260
2533
|
render() {
|
|
2261
2534
|
if (!this.isTTY) {
|
|
2262
2535
|
this.renderCI();
|
|
2263
2536
|
return;
|
|
2264
2537
|
}
|
|
2265
|
-
this.clearLines();
|
|
2266
2538
|
const lines = [];
|
|
2267
2539
|
lines.push("");
|
|
2268
2540
|
lines.push(` ${ROBOT[0]} ${BRAND[0]}`);
|
|
@@ -2303,8 +2575,21 @@ var ScanProgress = class {
|
|
|
2303
2575
|
lines.push("");
|
|
2304
2576
|
lines.push(this.renderStats());
|
|
2305
2577
|
lines.push("");
|
|
2306
|
-
const
|
|
2307
|
-
|
|
2578
|
+
const content = lines.join("\n") + "\n";
|
|
2579
|
+
if (content === this.lastFrame && this.lastLineCount === lines.length) {
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
this.lastFrame = content;
|
|
2583
|
+
let buf = "";
|
|
2584
|
+
if (this.lastLineCount > 0) {
|
|
2585
|
+
buf += `\x1B[${this.lastLineCount}A`;
|
|
2586
|
+
for (let i = 0; i < this.lastLineCount; i++) {
|
|
2587
|
+
buf += "\x1B[2K\n";
|
|
2588
|
+
}
|
|
2589
|
+
buf += `\x1B[${this.lastLineCount}A`;
|
|
2590
|
+
}
|
|
2591
|
+
buf += content;
|
|
2592
|
+
process.stderr.write(buf);
|
|
2308
2593
|
this.lastLineCount = lines.length;
|
|
2309
2594
|
}
|
|
2310
2595
|
renderStep(step) {
|
|
@@ -2323,6 +2608,11 @@ var ScanProgress = class {
|
|
|
2323
2608
|
if (step.subTotal && step.subTotal > 0 && step.subProgress !== void 0 && step.subProgress > 0) {
|
|
2324
2609
|
detail = chalk4.dim(` \xB7 ${step.subProgress.toLocaleString()} / ${step.subTotal.toLocaleString()}`);
|
|
2325
2610
|
}
|
|
2611
|
+
if (step.subLabel) {
|
|
2612
|
+
const maxLen = 50;
|
|
2613
|
+
const displayPath = step.subLabel.length > maxLen ? "\u2026" + step.subLabel.slice(-maxLen + 1) : step.subLabel;
|
|
2614
|
+
detail += chalk4.dim(` ${displayPath}`);
|
|
2615
|
+
}
|
|
2326
2616
|
break;
|
|
2327
2617
|
case "skipped":
|
|
2328
2618
|
icon = chalk4.dim("\u25CC");
|
|
@@ -2428,11 +2718,11 @@ var ScanProgress = class {
|
|
|
2428
2718
|
|
|
2429
2719
|
// src/ui/scan-history.ts
|
|
2430
2720
|
import * as fs4 from "fs/promises";
|
|
2431
|
-
import * as
|
|
2721
|
+
import * as path9 from "path";
|
|
2432
2722
|
var HISTORY_FILENAME = "scan_history.json";
|
|
2433
2723
|
var MAX_RECORDS = 10;
|
|
2434
2724
|
async function loadScanHistory(rootDir) {
|
|
2435
|
-
const filePath =
|
|
2725
|
+
const filePath = path9.join(rootDir, ".vibgrate", HISTORY_FILENAME);
|
|
2436
2726
|
try {
|
|
2437
2727
|
const txt = await fs4.readFile(filePath, "utf8");
|
|
2438
2728
|
const data = JSON.parse(txt);
|
|
@@ -2445,8 +2735,8 @@ async function loadScanHistory(rootDir) {
|
|
|
2445
2735
|
}
|
|
2446
2736
|
}
|
|
2447
2737
|
async function saveScanHistory(rootDir, record) {
|
|
2448
|
-
const dir =
|
|
2449
|
-
const filePath =
|
|
2738
|
+
const dir = path9.join(rootDir, ".vibgrate");
|
|
2739
|
+
const filePath = path9.join(dir, HISTORY_FILENAME);
|
|
2450
2740
|
let history;
|
|
2451
2741
|
const existing = await loadScanHistory(rootDir);
|
|
2452
2742
|
if (existing) {
|
|
@@ -2510,7 +2800,7 @@ function estimateStepDurations(history, currentFileCount) {
|
|
|
2510
2800
|
}
|
|
2511
2801
|
|
|
2512
2802
|
// src/scanners/platform-matrix.ts
|
|
2513
|
-
import * as
|
|
2803
|
+
import * as path10 from "path";
|
|
2514
2804
|
var NATIVE_MODULE_PACKAGES = /* @__PURE__ */ new Set([
|
|
2515
2805
|
// Image / media processing
|
|
2516
2806
|
"sharp",
|
|
@@ -2790,7 +3080,7 @@ async function scanPlatformMatrix(rootDir, cache) {
|
|
|
2790
3080
|
}
|
|
2791
3081
|
result.dockerBaseImages = [...baseImages].sort();
|
|
2792
3082
|
for (const file of [".nvmrc", ".node-version", ".tool-versions"]) {
|
|
2793
|
-
const exists = cache ? await cache.pathExists(
|
|
3083
|
+
const exists = cache ? await cache.pathExists(path10.join(rootDir, file)) : await pathExists(path10.join(rootDir, file));
|
|
2794
3084
|
if (exists) {
|
|
2795
3085
|
result.nodeVersionFiles.push(file);
|
|
2796
3086
|
}
|
|
@@ -2867,7 +3157,7 @@ function scanDependencyRisk(projects) {
|
|
|
2867
3157
|
}
|
|
2868
3158
|
|
|
2869
3159
|
// src/scanners/dependency-graph.ts
|
|
2870
|
-
import * as
|
|
3160
|
+
import * as path11 from "path";
|
|
2871
3161
|
function parsePnpmLock(content) {
|
|
2872
3162
|
const entries = [];
|
|
2873
3163
|
const regex = /^\s+\/?(@?[^@\s][^@\s]*?)@(\d+\.\d+\.\d+[^:\s]*)\s*:/gm;
|
|
@@ -2926,9 +3216,9 @@ async function scanDependencyGraph(rootDir, cache) {
|
|
|
2926
3216
|
phantomDependencies: []
|
|
2927
3217
|
};
|
|
2928
3218
|
let entries = [];
|
|
2929
|
-
const pnpmLock =
|
|
2930
|
-
const npmLock =
|
|
2931
|
-
const yarnLock =
|
|
3219
|
+
const pnpmLock = path11.join(rootDir, "pnpm-lock.yaml");
|
|
3220
|
+
const npmLock = path11.join(rootDir, "package-lock.json");
|
|
3221
|
+
const yarnLock = path11.join(rootDir, "yarn.lock");
|
|
2932
3222
|
const _pathExists = cache ? (p) => cache.pathExists(p) : pathExists;
|
|
2933
3223
|
const _readTextFile = cache ? (p) => cache.readTextFile(p) : readTextFile;
|
|
2934
3224
|
if (await _pathExists(pnpmLock)) {
|
|
@@ -2975,7 +3265,7 @@ async function scanDependencyGraph(rootDir, cache) {
|
|
|
2975
3265
|
for (const pjPath of pkgFiles) {
|
|
2976
3266
|
try {
|
|
2977
3267
|
const pj = cache ? await cache.readJsonFile(pjPath) : await readJsonFile(pjPath);
|
|
2978
|
-
const relPath =
|
|
3268
|
+
const relPath = path11.relative(rootDir, pjPath);
|
|
2979
3269
|
for (const section of ["dependencies", "devDependencies"]) {
|
|
2980
3270
|
const deps = pj[section];
|
|
2981
3271
|
if (!deps) continue;
|
|
@@ -3321,7 +3611,7 @@ function scanToolingInventory(projects) {
|
|
|
3321
3611
|
}
|
|
3322
3612
|
|
|
3323
3613
|
// src/scanners/build-deploy.ts
|
|
3324
|
-
import * as
|
|
3614
|
+
import * as path12 from "path";
|
|
3325
3615
|
var CI_FILES = {
|
|
3326
3616
|
".github/workflows": "github-actions",
|
|
3327
3617
|
".gitlab-ci.yml": "gitlab-ci",
|
|
@@ -3374,17 +3664,17 @@ async function scanBuildDeploy(rootDir, cache) {
|
|
|
3374
3664
|
const _readTextFile = cache ? (p) => cache.readTextFile(p) : readTextFile;
|
|
3375
3665
|
const ciSystems = /* @__PURE__ */ new Set();
|
|
3376
3666
|
for (const [file, system] of Object.entries(CI_FILES)) {
|
|
3377
|
-
const fullPath =
|
|
3667
|
+
const fullPath = path12.join(rootDir, file);
|
|
3378
3668
|
if (await _pathExists(fullPath)) {
|
|
3379
3669
|
ciSystems.add(system);
|
|
3380
3670
|
}
|
|
3381
3671
|
}
|
|
3382
|
-
const ghWorkflowDir =
|
|
3672
|
+
const ghWorkflowDir = path12.join(rootDir, ".github", "workflows");
|
|
3383
3673
|
if (await _pathExists(ghWorkflowDir)) {
|
|
3384
3674
|
try {
|
|
3385
3675
|
if (cache) {
|
|
3386
3676
|
const entries = await cache.walkDir(rootDir);
|
|
3387
|
-
const ghPrefix =
|
|
3677
|
+
const ghPrefix = path12.relative(rootDir, ghWorkflowDir) + path12.sep;
|
|
3388
3678
|
result.ciWorkflowCount = entries.filter(
|
|
3389
3679
|
(e) => e.isFile && e.relPath.startsWith(ghPrefix) && (e.name.endsWith(".yml") || e.name.endsWith(".yaml"))
|
|
3390
3680
|
).length;
|
|
@@ -3435,11 +3725,11 @@ async function scanBuildDeploy(rootDir, cache) {
|
|
|
3435
3725
|
(name) => name.endsWith(".cfn.json") || name.endsWith(".cfn.yaml")
|
|
3436
3726
|
);
|
|
3437
3727
|
if (cfnFiles.length > 0) iacSystems.add("cloudformation");
|
|
3438
|
-
if (await _pathExists(
|
|
3728
|
+
if (await _pathExists(path12.join(rootDir, "Pulumi.yaml"))) iacSystems.add("pulumi");
|
|
3439
3729
|
result.iac = [...iacSystems].sort();
|
|
3440
3730
|
const releaseTools = /* @__PURE__ */ new Set();
|
|
3441
3731
|
for (const [file, tool] of Object.entries(RELEASE_FILES)) {
|
|
3442
|
-
if (await _pathExists(
|
|
3732
|
+
if (await _pathExists(path12.join(rootDir, file))) releaseTools.add(tool);
|
|
3443
3733
|
}
|
|
3444
3734
|
const pkgFiles = cache ? await cache.findPackageJsonFiles(rootDir) : await findPackageJsonFiles(rootDir);
|
|
3445
3735
|
for (const pjPath of pkgFiles) {
|
|
@@ -3464,19 +3754,19 @@ async function scanBuildDeploy(rootDir, cache) {
|
|
|
3464
3754
|
};
|
|
3465
3755
|
const managers = /* @__PURE__ */ new Set();
|
|
3466
3756
|
for (const [file, manager] of Object.entries(lockfileMap)) {
|
|
3467
|
-
if (await _pathExists(
|
|
3757
|
+
if (await _pathExists(path12.join(rootDir, file))) managers.add(manager);
|
|
3468
3758
|
}
|
|
3469
3759
|
result.packageManagers = [...managers].sort();
|
|
3470
3760
|
const monoTools = /* @__PURE__ */ new Set();
|
|
3471
3761
|
for (const [file, tool] of Object.entries(MONOREPO_FILES)) {
|
|
3472
|
-
if (await _pathExists(
|
|
3762
|
+
if (await _pathExists(path12.join(rootDir, file))) monoTools.add(tool);
|
|
3473
3763
|
}
|
|
3474
3764
|
result.monorepoTools = [...monoTools].sort();
|
|
3475
3765
|
return result;
|
|
3476
3766
|
}
|
|
3477
3767
|
|
|
3478
3768
|
// src/scanners/ts-modernity.ts
|
|
3479
|
-
import * as
|
|
3769
|
+
import * as path13 from "path";
|
|
3480
3770
|
async function scanTsModernity(rootDir, cache) {
|
|
3481
3771
|
const result = {
|
|
3482
3772
|
typescriptVersion: null,
|
|
@@ -3514,7 +3804,7 @@ async function scanTsModernity(rootDir, cache) {
|
|
|
3514
3804
|
if (hasEsm && hasCjs) result.moduleType = "mixed";
|
|
3515
3805
|
else if (hasEsm) result.moduleType = "esm";
|
|
3516
3806
|
else if (hasCjs) result.moduleType = "cjs";
|
|
3517
|
-
let tsConfigPath =
|
|
3807
|
+
let tsConfigPath = path13.join(rootDir, "tsconfig.json");
|
|
3518
3808
|
const tsConfigExists = cache ? await cache.pathExists(tsConfigPath) : await pathExists(tsConfigPath);
|
|
3519
3809
|
if (!tsConfigExists) {
|
|
3520
3810
|
const tsConfigs = cache ? await cache.findFiles(rootDir, (name) => name === "tsconfig.json") : await findFiles(rootDir, (name) => name === "tsconfig.json");
|
|
@@ -3861,7 +4151,7 @@ function scanBreakingChangeExposure(projects) {
|
|
|
3861
4151
|
|
|
3862
4152
|
// src/scanners/file-hotspots.ts
|
|
3863
4153
|
import * as fs5 from "fs/promises";
|
|
3864
|
-
import * as
|
|
4154
|
+
import * as path14 from "path";
|
|
3865
4155
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
3866
4156
|
"node_modules",
|
|
3867
4157
|
".git",
|
|
@@ -3904,16 +4194,16 @@ async function scanFileHotspots(rootDir, cache) {
|
|
|
3904
4194
|
const entries = await cache.walkDir(rootDir);
|
|
3905
4195
|
for (const entry of entries) {
|
|
3906
4196
|
if (!entry.isFile) continue;
|
|
3907
|
-
const ext =
|
|
4197
|
+
const ext = path14.extname(entry.name).toLowerCase();
|
|
3908
4198
|
if (SKIP_EXTENSIONS.has(ext)) continue;
|
|
3909
|
-
const depth = entry.relPath.split(
|
|
4199
|
+
const depth = entry.relPath.split(path14.sep).length - 1;
|
|
3910
4200
|
if (depth > maxDepth) maxDepth = depth;
|
|
3911
4201
|
extensionCounts[ext] = (extensionCounts[ext] ?? 0) + 1;
|
|
3912
4202
|
try {
|
|
3913
|
-
const
|
|
4203
|
+
const stat4 = await fs5.stat(entry.absPath);
|
|
3914
4204
|
allFiles.push({
|
|
3915
4205
|
path: entry.relPath,
|
|
3916
|
-
bytes:
|
|
4206
|
+
bytes: stat4.size
|
|
3917
4207
|
});
|
|
3918
4208
|
} catch {
|
|
3919
4209
|
}
|
|
@@ -3935,16 +4225,16 @@ async function scanFileHotspots(rootDir, cache) {
|
|
|
3935
4225
|
for (const e of entries) {
|
|
3936
4226
|
if (e.isDirectory) {
|
|
3937
4227
|
if (SKIP_DIRS2.has(e.name)) continue;
|
|
3938
|
-
await walk(
|
|
4228
|
+
await walk(path14.join(dir, e.name), depth + 1);
|
|
3939
4229
|
} else if (e.isFile) {
|
|
3940
|
-
const ext =
|
|
4230
|
+
const ext = path14.extname(e.name).toLowerCase();
|
|
3941
4231
|
if (SKIP_EXTENSIONS.has(ext)) continue;
|
|
3942
4232
|
extensionCounts[ext] = (extensionCounts[ext] ?? 0) + 1;
|
|
3943
4233
|
try {
|
|
3944
|
-
const
|
|
4234
|
+
const stat4 = await fs5.stat(path14.join(dir, e.name));
|
|
3945
4235
|
allFiles.push({
|
|
3946
|
-
path:
|
|
3947
|
-
bytes:
|
|
4236
|
+
path: path14.relative(rootDir, path14.join(dir, e.name)),
|
|
4237
|
+
bytes: stat4.size
|
|
3948
4238
|
});
|
|
3949
4239
|
} catch {
|
|
3950
4240
|
}
|
|
@@ -3966,7 +4256,7 @@ async function scanFileHotspots(rootDir, cache) {
|
|
|
3966
4256
|
}
|
|
3967
4257
|
|
|
3968
4258
|
// src/scanners/security-posture.ts
|
|
3969
|
-
import * as
|
|
4259
|
+
import * as path15 from "path";
|
|
3970
4260
|
var LOCKFILES = {
|
|
3971
4261
|
"pnpm-lock.yaml": "pnpm",
|
|
3972
4262
|
"package-lock.json": "npm",
|
|
@@ -3987,14 +4277,14 @@ async function scanSecurityPosture(rootDir, cache) {
|
|
|
3987
4277
|
const _readTextFile = cache ? (p) => cache.readTextFile(p) : readTextFile;
|
|
3988
4278
|
const foundLockfiles = [];
|
|
3989
4279
|
for (const [file, type] of Object.entries(LOCKFILES)) {
|
|
3990
|
-
if (await _pathExists(
|
|
4280
|
+
if (await _pathExists(path15.join(rootDir, file))) {
|
|
3991
4281
|
foundLockfiles.push(type);
|
|
3992
4282
|
}
|
|
3993
4283
|
}
|
|
3994
4284
|
result.lockfilePresent = foundLockfiles.length > 0;
|
|
3995
4285
|
result.multipleLockfileTypes = foundLockfiles.length > 1;
|
|
3996
4286
|
result.lockfileTypes = foundLockfiles.sort();
|
|
3997
|
-
const gitignorePath =
|
|
4287
|
+
const gitignorePath = path15.join(rootDir, ".gitignore");
|
|
3998
4288
|
if (await _pathExists(gitignorePath)) {
|
|
3999
4289
|
try {
|
|
4000
4290
|
const content = await _readTextFile(gitignorePath);
|
|
@@ -4009,7 +4299,7 @@ async function scanSecurityPosture(rootDir, cache) {
|
|
|
4009
4299
|
}
|
|
4010
4300
|
}
|
|
4011
4301
|
for (const envFile of [".env", ".env.local", ".env.development", ".env.production"]) {
|
|
4012
|
-
if (await _pathExists(
|
|
4302
|
+
if (await _pathExists(path15.join(rootDir, envFile))) {
|
|
4013
4303
|
if (!result.gitignoreCoversEnv) {
|
|
4014
4304
|
result.envFilesTracked = true;
|
|
4015
4305
|
break;
|
|
@@ -4434,7 +4724,7 @@ function scanServiceDependencies(projects) {
|
|
|
4434
4724
|
}
|
|
4435
4725
|
|
|
4436
4726
|
// src/scanners/architecture.ts
|
|
4437
|
-
import * as
|
|
4727
|
+
import * as path16 from "path";
|
|
4438
4728
|
import * as fs6 from "fs/promises";
|
|
4439
4729
|
var ARCHETYPE_SIGNALS = [
|
|
4440
4730
|
// Meta-frameworks (highest priority — they imply routing patterns)
|
|
@@ -4733,9 +5023,9 @@ async function walkSourceFiles(rootDir, cache) {
|
|
|
4733
5023
|
const entries = await cache.walkDir(rootDir);
|
|
4734
5024
|
return entries.filter((e) => {
|
|
4735
5025
|
if (!e.isFile) return false;
|
|
4736
|
-
const name =
|
|
5026
|
+
const name = path16.basename(e.absPath);
|
|
4737
5027
|
if (name.startsWith(".") && name !== ".") return false;
|
|
4738
|
-
const ext =
|
|
5028
|
+
const ext = path16.extname(name);
|
|
4739
5029
|
return SOURCE_EXTENSIONS.has(ext);
|
|
4740
5030
|
}).map((e) => e.relPath);
|
|
4741
5031
|
}
|
|
@@ -4749,15 +5039,15 @@ async function walkSourceFiles(rootDir, cache) {
|
|
|
4749
5039
|
}
|
|
4750
5040
|
for (const entry of entries) {
|
|
4751
5041
|
if (entry.name.startsWith(".") && entry.name !== ".") continue;
|
|
4752
|
-
const fullPath =
|
|
5042
|
+
const fullPath = path16.join(dir, entry.name);
|
|
4753
5043
|
if (entry.isDirectory()) {
|
|
4754
5044
|
if (!IGNORE_DIRS.has(entry.name)) {
|
|
4755
5045
|
await walk(fullPath);
|
|
4756
5046
|
}
|
|
4757
5047
|
} else if (entry.isFile()) {
|
|
4758
|
-
const ext =
|
|
5048
|
+
const ext = path16.extname(entry.name);
|
|
4759
5049
|
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
4760
|
-
files.push(
|
|
5050
|
+
files.push(path16.relative(rootDir, fullPath));
|
|
4761
5051
|
}
|
|
4762
5052
|
}
|
|
4763
5053
|
}
|
|
@@ -4781,7 +5071,7 @@ function classifyFile(filePath, archetype) {
|
|
|
4781
5071
|
}
|
|
4782
5072
|
}
|
|
4783
5073
|
if (!bestMatch || bestMatch.confidence < 0.7) {
|
|
4784
|
-
const baseName =
|
|
5074
|
+
const baseName = path16.basename(filePath, path16.extname(filePath));
|
|
4785
5075
|
const cleanBase = baseName.replace(/\.(test|spec)$/, "");
|
|
4786
5076
|
for (const rule of SUFFIX_RULES) {
|
|
4787
5077
|
if (cleanBase.endsWith(rule.suffix)) {
|
|
@@ -4972,6 +5262,9 @@ async function runScan(rootDir, opts) {
|
|
|
4972
5262
|
const sem = new Semaphore(opts.concurrency);
|
|
4973
5263
|
const npmCache = new NpmCache(rootDir, sem);
|
|
4974
5264
|
const fileCache = new FileCache();
|
|
5265
|
+
const excludePatterns = config.exclude ?? [];
|
|
5266
|
+
fileCache.setExcludePatterns(excludePatterns);
|
|
5267
|
+
fileCache.setMaxFileSize(config.maxFileSizeToScan ?? 5242880);
|
|
4975
5268
|
const scanners = config.scanners;
|
|
4976
5269
|
let filesScanned = 0;
|
|
4977
5270
|
const progress = new ScanProgress(rootDir);
|
|
@@ -5001,7 +5294,7 @@ async function runScan(rootDir, opts) {
|
|
|
5001
5294
|
progress.setSteps(steps);
|
|
5002
5295
|
progress.completeStep("config", "loaded");
|
|
5003
5296
|
progress.startStep("discovery");
|
|
5004
|
-
const treeCount = await quickTreeCount(rootDir);
|
|
5297
|
+
const treeCount = await quickTreeCount(rootDir, excludePatterns);
|
|
5005
5298
|
progress.updateStats({ treeSummary: treeCount });
|
|
5006
5299
|
progress.completeStep(
|
|
5007
5300
|
"discovery",
|
|
@@ -5016,8 +5309,8 @@ async function runScan(rootDir, opts) {
|
|
|
5016
5309
|
const vcsDetail = vcs.type !== "unknown" ? `${vcs.type}${vcs.branch ? ` ${vcs.branch}` : ""}${vcs.shortSha ? ` @ ${vcs.shortSha}` : ""}` : "none detected";
|
|
5017
5310
|
progress.completeStep("vcs", vcsDetail);
|
|
5018
5311
|
progress.startStep("walk", treeCount.totalFiles);
|
|
5019
|
-
await fileCache.walkDir(rootDir, (found) => {
|
|
5020
|
-
progress.updateStepProgress("walk", found, treeCount.totalFiles);
|
|
5312
|
+
await fileCache.walkDir(rootDir, (found, currentPath) => {
|
|
5313
|
+
progress.updateStepProgress("walk", found, treeCount.totalFiles, currentPath);
|
|
5021
5314
|
});
|
|
5022
5315
|
progress.completeStep("walk", `${treeCount.totalFiles.toLocaleString()} files indexed`);
|
|
5023
5316
|
progress.startStep("node");
|
|
@@ -5199,6 +5492,36 @@ async function runScan(rootDir, opts) {
|
|
|
5199
5492
|
if (noteCount > 0) findingParts.push(`${noteCount} note${noteCount !== 1 ? "s" : ""}`);
|
|
5200
5493
|
progress.completeStep("findings", findingParts.join(", ") || "none");
|
|
5201
5494
|
progress.finish();
|
|
5495
|
+
const stuckPaths = fileCache.stuckPaths;
|
|
5496
|
+
const skippedLarge = fileCache.skippedLargeFiles;
|
|
5497
|
+
if (stuckPaths.length > 0) {
|
|
5498
|
+
console.log(
|
|
5499
|
+
chalk5.yellow(`
|
|
5500
|
+
\u26A0 ${stuckPaths.length} path${stuckPaths.length === 1 ? "" : "s"} timed out (>60s) and ${stuckPaths.length === 1 ? "was" : "were"} skipped:`)
|
|
5501
|
+
);
|
|
5502
|
+
for (const d of stuckPaths) {
|
|
5503
|
+
console.log(chalk5.dim(` \u2192 ${d}`));
|
|
5504
|
+
}
|
|
5505
|
+
const newExcludes = stuckPaths.map((d) => `${d}/**`);
|
|
5506
|
+
const updated = await appendExcludePatterns(rootDir, newExcludes);
|
|
5507
|
+
if (updated) {
|
|
5508
|
+
console.log(chalk5.green("\u2714") + ` Added ${newExcludes.length} pattern${newExcludes.length !== 1 ? "s" : ""} to exclude list in config`);
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
5511
|
+
if (skippedLarge.length > 0) {
|
|
5512
|
+
const sizeLimit = config.maxFileSizeToScan ?? 5242880;
|
|
5513
|
+
const sizeMB = (sizeLimit / 1048576).toFixed(0);
|
|
5514
|
+
console.log(
|
|
5515
|
+
chalk5.yellow(`
|
|
5516
|
+
\u26A0 ${skippedLarge.length} file${skippedLarge.length === 1 ? "" : "s"} skipped (>${sizeMB} MB):`)
|
|
5517
|
+
);
|
|
5518
|
+
for (const f of skippedLarge.slice(0, 10)) {
|
|
5519
|
+
console.log(chalk5.dim(` \u2192 ${f}`));
|
|
5520
|
+
}
|
|
5521
|
+
if (skippedLarge.length > 10) {
|
|
5522
|
+
console.log(chalk5.dim(` \u2026 and ${skippedLarge.length - 10} more`));
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5202
5525
|
fileCache.clear();
|
|
5203
5526
|
if (allProjects.length === 0) {
|
|
5204
5527
|
console.log(chalk5.yellow("No projects found."));
|
|
@@ -5216,7 +5539,7 @@ async function runScan(rootDir, opts) {
|
|
|
5216
5539
|
schemaVersion: "1.0",
|
|
5217
5540
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5218
5541
|
vibgrateVersion: VERSION,
|
|
5219
|
-
rootPath:
|
|
5542
|
+
rootPath: path17.basename(rootDir),
|
|
5220
5543
|
...vcs.type !== "unknown" ? { vcs } : {},
|
|
5221
5544
|
projects: allProjects,
|
|
5222
5545
|
drift,
|
|
@@ -5227,7 +5550,7 @@ async function runScan(rootDir, opts) {
|
|
|
5227
5550
|
treeSummary: treeCount
|
|
5228
5551
|
};
|
|
5229
5552
|
if (opts.baseline) {
|
|
5230
|
-
const baselinePath =
|
|
5553
|
+
const baselinePath = path17.resolve(opts.baseline);
|
|
5231
5554
|
if (await pathExists(baselinePath)) {
|
|
5232
5555
|
try {
|
|
5233
5556
|
const baseline = await readJsonFile(baselinePath);
|
|
@@ -5238,9 +5561,9 @@ async function runScan(rootDir, opts) {
|
|
|
5238
5561
|
}
|
|
5239
5562
|
}
|
|
5240
5563
|
}
|
|
5241
|
-
const vibgrateDir =
|
|
5564
|
+
const vibgrateDir = path17.join(rootDir, ".vibgrate");
|
|
5242
5565
|
await ensureDir(vibgrateDir);
|
|
5243
|
-
await writeJsonFile(
|
|
5566
|
+
await writeJsonFile(path17.join(vibgrateDir, "scan_result.json"), artifact);
|
|
5244
5567
|
await saveScanHistory(rootDir, {
|
|
5245
5568
|
timestamp: artifact.timestamp,
|
|
5246
5569
|
totalDurationMs: durationMs,
|
|
@@ -5250,10 +5573,10 @@ async function runScan(rootDir, opts) {
|
|
|
5250
5573
|
});
|
|
5251
5574
|
for (const project of allProjects) {
|
|
5252
5575
|
if (project.drift && project.path) {
|
|
5253
|
-
const projectDir =
|
|
5254
|
-
const projectVibgrateDir =
|
|
5576
|
+
const projectDir = path17.resolve(rootDir, project.path);
|
|
5577
|
+
const projectVibgrateDir = path17.join(projectDir, ".vibgrate");
|
|
5255
5578
|
await ensureDir(projectVibgrateDir);
|
|
5256
|
-
await writeJsonFile(
|
|
5579
|
+
await writeJsonFile(path17.join(projectVibgrateDir, "project_score.json"), {
|
|
5257
5580
|
projectId: project.projectId,
|
|
5258
5581
|
name: project.name,
|
|
5259
5582
|
type: project.type,
|
|
@@ -5270,7 +5593,7 @@ async function runScan(rootDir, opts) {
|
|
|
5270
5593
|
if (opts.format === "json") {
|
|
5271
5594
|
const jsonStr = JSON.stringify(artifact, null, 2);
|
|
5272
5595
|
if (opts.out) {
|
|
5273
|
-
await writeTextFile(
|
|
5596
|
+
await writeTextFile(path17.resolve(opts.out), jsonStr);
|
|
5274
5597
|
console.log(chalk5.green("\u2714") + ` JSON written to ${opts.out}`);
|
|
5275
5598
|
} else {
|
|
5276
5599
|
console.log(jsonStr);
|
|
@@ -5279,7 +5602,7 @@ async function runScan(rootDir, opts) {
|
|
|
5279
5602
|
const sarif = formatSarif(artifact);
|
|
5280
5603
|
const sarifStr = JSON.stringify(sarif, null, 2);
|
|
5281
5604
|
if (opts.out) {
|
|
5282
|
-
await writeTextFile(
|
|
5605
|
+
await writeTextFile(path17.resolve(opts.out), sarifStr);
|
|
5283
5606
|
console.log(chalk5.green("\u2714") + ` SARIF written to ${opts.out}`);
|
|
5284
5607
|
} else {
|
|
5285
5608
|
console.log(sarifStr);
|
|
@@ -5288,7 +5611,7 @@ async function runScan(rootDir, opts) {
|
|
|
5288
5611
|
const text = formatText(artifact);
|
|
5289
5612
|
console.log(text);
|
|
5290
5613
|
if (opts.out) {
|
|
5291
|
-
await writeTextFile(
|
|
5614
|
+
await writeTextFile(path17.resolve(opts.out), text);
|
|
5292
5615
|
}
|
|
5293
5616
|
}
|
|
5294
5617
|
return artifact;
|
|
@@ -5347,7 +5670,7 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
5347
5670
|
}
|
|
5348
5671
|
}
|
|
5349
5672
|
var scanCommand = new Command3("scan").description("Scan a project for upgrade drift").argument("[path]", "Path to scan", ".").option("--out <file>", "Output file path").option("--format <format>", "Output format (text|json|sarif)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").action(async (targetPath, opts) => {
|
|
5350
|
-
const rootDir =
|
|
5673
|
+
const rootDir = path17.resolve(targetPath);
|
|
5351
5674
|
if (!await pathExists(rootDir)) {
|
|
5352
5675
|
console.error(chalk5.red(`Path does not exist: ${rootDir}`));
|
|
5353
5676
|
process.exit(1);
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-GN3IWKSY.js";
|
|
5
5
|
import {
|
|
6
6
|
baselineCommand
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IMK7DUPY.js";
|
|
8
8
|
import {
|
|
9
9
|
VERSION,
|
|
10
10
|
dsnCommand,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
readJsonFile,
|
|
16
16
|
scanCommand,
|
|
17
17
|
writeDefaultConfig
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-JFMGFWKC.js";
|
|
19
19
|
|
|
20
20
|
// src/cli.ts
|
|
21
21
|
import { Command as Command4 } from "commander";
|
|
@@ -38,7 +38,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
38
38
|
console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
|
|
39
39
|
}
|
|
40
40
|
if (opts.baseline) {
|
|
41
|
-
const { runBaseline } = await import("./baseline-
|
|
41
|
+
const { runBaseline } = await import("./baseline-IOXJPCX7.js");
|
|
42
42
|
await runBaseline(rootDir);
|
|
43
43
|
}
|
|
44
44
|
console.log("");
|
package/dist/index.d.ts
CHANGED
|
@@ -123,6 +123,9 @@ interface ScannersConfig {
|
|
|
123
123
|
interface VibgrateConfig {
|
|
124
124
|
include?: string[];
|
|
125
125
|
exclude?: string[];
|
|
126
|
+
/** Maximum file size (bytes) the CLI will read during a scan. Files larger
|
|
127
|
+
* than this are silently skipped. Default: 5 242 880 (5 MB). */
|
|
128
|
+
maxFileSizeToScan?: number;
|
|
126
129
|
scanners?: ScannersConfig | false;
|
|
127
130
|
thresholds?: {
|
|
128
131
|
failOnError?: {
|
package/dist/index.js
CHANGED