@vibgrate/cli 2026.610.1 → 2026.611.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DOCS.md +43 -1
- package/README.md +15 -3
- package/dist/{baseline-Q3S7D7RQ.js → baseline-QRGGTCJD.js} +3 -3
- package/dist/{chunk-HQCB2BTS.js → chunk-4CDBCG4I.js} +5 -2
- package/dist/{chunk-RAQ76CZO.js → chunk-C7LU6YIL.js} +24 -3
- package/dist/{chunk-X2MPRPZ6.js → chunk-EIZZ6VC3.js} +111 -17
- package/dist/cli.js +15 -5
- package/dist/{fs-CMSVYM7J-QRLLRZ2J.js → fs-PXXYZATK-EW5LCUA7.js} +3 -1
- package/dist/hcs-worker.js +2295 -3
- package/dist/index.js +2 -2
- package/package.json +3 -2
package/DOCS.md
CHANGED
|
@@ -162,7 +162,7 @@ Creates:
|
|
|
162
162
|
The primary command. Scans your project for upgrade drift.
|
|
163
163
|
|
|
164
164
|
```bash
|
|
165
|
-
vibgrate scan [path] [--format text|json|sarif|md] [--out <file>] [--fail-on warn|error] [--offline] [--package-manifest <file>] [--no-local-artifacts] [--max-privacy] [--baseline <file>] [--drift-budget <score>] [--drift-worsening <percent>] [--changed-only] [--concurrency <n>]
|
|
165
|
+
vibgrate scan [path] [--format text|json|sarif|md] [--out <file>] [--fail-on warn|error] [--offline] [--package-manifest <file>] [--no-local-artifacts] [--max-privacy] [--baseline <file>] [--drift-budget <score>] [--drift-worsening <percent>] [--changed-only] [--exclude <glob>...] [--concurrency <n>]
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
| Flag | Default | Description |
|
|
@@ -172,6 +172,7 @@ vibgrate scan [path] [--format text|json|sarif|md] [--out <file>] [--fail-on war
|
|
|
172
172
|
| `--fail-on <level>` | — | Exit with code 2 if findings at this level exist |
|
|
173
173
|
| `--baseline <file>` | — | Compare against a previous baseline |
|
|
174
174
|
| `--changed-only` | — | Only scan changed files |
|
|
175
|
+
| `-e, --exclude <glob>` | — | Exclude paths matching a glob pattern. Repeatable, and a single value may list several patterns separated by commas or semicolons. Merged with `exclude` from the config file |
|
|
175
176
|
| `--concurrency <n>` | `8` | Max concurrent npm registry calls |
|
|
176
177
|
| `--drift-budget <score>` | — | Fitness gate: fail if drift score is above this budget |
|
|
177
178
|
| `--drift-worsening <percent>` | — | Fitness gate: fail if drift worsens by more than % vs baseline |
|
|
@@ -189,6 +190,32 @@ By default, the scan writes `.vibgrate/scan_result.json`. Use `--no-local-artifa
|
|
|
189
190
|
|
|
190
191
|
For offline drift scoring, pass `--package-manifest <file>` with a downloaded manifest bundle such as `https://github.com/vibgrate/manifests/latest-packages.zip`.
|
|
191
192
|
|
|
193
|
+
#### Excluding paths from a scan
|
|
194
|
+
|
|
195
|
+
Use `--exclude` (alias `-e`) to skip directories or files from the scan. Values are [glob patterns](#exclude-glob-syntax) matched against repository-relative, forward-slash paths. The flag is **repeatable**, and a single value may carry **several patterns separated by commas or semicolons** — use whichever reads best:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Repeat the flag
|
|
199
|
+
vibgrate scan . --exclude "legacy/**" --exclude "vendor/**"
|
|
200
|
+
|
|
201
|
+
# Or list multiple patterns in one flag (comma- or semicolon-separated)
|
|
202
|
+
vibgrate scan . --exclude "legacy/**,vendor/**;**/fixtures/**"
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
CLI excludes are **additive**: they are merged (and de-duplicated) with any `exclude` patterns from your [config file](#configuration), so command-line excludes never replace your committed defaults.
|
|
206
|
+
|
|
207
|
+
<a id="exclude-glob-syntax"></a>
|
|
208
|
+
Supported glob syntax:
|
|
209
|
+
|
|
210
|
+
| Pattern | Matches |
|
|
211
|
+
|---------|---------|
|
|
212
|
+
| `*` | Any run of characters within a single path segment (no `/`) |
|
|
213
|
+
| `**` | Any number of path segments, including `/` (recursive) |
|
|
214
|
+
| `?` | Exactly one non-separator character |
|
|
215
|
+
| `{a,b}` | Alternation — either `a` or `b` |
|
|
216
|
+
| `[abc]` | A single character from the set |
|
|
217
|
+
| `legacy` | A bare name is treated as a directory prefix — equivalent to `legacy/**` |
|
|
218
|
+
|
|
192
219
|
Examples:
|
|
193
220
|
|
|
194
221
|
```bash
|
|
@@ -198,6 +225,9 @@ vibgrate scan .
|
|
|
198
225
|
# JSON output for automation
|
|
199
226
|
vibgrate scan . --format json --out scan.json
|
|
200
227
|
|
|
228
|
+
# Skip vendored and generated code
|
|
229
|
+
vibgrate scan . --exclude "vendor/**,**/*.generated.ts;dist/**"
|
|
230
|
+
|
|
201
231
|
# CI gate with baseline regression protection
|
|
202
232
|
vibgrate scan . --baseline .vibgrate/baseline.json --drift-budget 40 --drift-worsening 5 --fail-on error
|
|
203
233
|
|
|
@@ -433,6 +463,18 @@ export default config;
|
|
|
433
463
|
|
|
434
464
|
Also supports `vibgrate.config.js` and `vibgrate.config.json`.
|
|
435
465
|
|
|
466
|
+
### Exclude patterns
|
|
467
|
+
|
|
468
|
+
`exclude` accepts an array of glob patterns describing paths the scan should skip (relative to the repository root, forward-slash separated). See [exclude glob syntax](#exclude-glob-syntax) for the supported patterns.
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
const config: VibgrateConfig = {
|
|
472
|
+
exclude: ["legacy/**", "vendor/**", "**/*.generated.ts"],
|
|
473
|
+
};
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
The same patterns can be supplied per-run on the command line with [`--exclude`](#excluding-paths-from-a-scan). CLI excludes are merged with (not a replacement for) the config `exclude` list, so committed defaults always apply and ad-hoc excludes are additive.
|
|
477
|
+
|
|
436
478
|
### Thresholds
|
|
437
479
|
|
|
438
480
|
Control when findings are raised and when the CLI should fail.
|
package/README.md
CHANGED
|
@@ -193,7 +193,7 @@ When offline mode runs without a package manifest, package freshness is marked a
|
|
|
193
193
|
## Core commands
|
|
194
194
|
|
|
195
195
|
```bash
|
|
196
|
-
vibgrate scan [path] [--format text|json|sarif|md] [--out <file>] [--fail-on warn|error] [--offline] [--package-manifest <file>] [--no-local-artifacts] [--max-privacy]
|
|
196
|
+
vibgrate scan [path] [--format text|json|sarif|md] [--out <file>] [--fail-on warn|error] [--exclude <glob>...] [--offline] [--package-manifest <file>] [--no-local-artifacts] [--max-privacy]
|
|
197
197
|
vibgrate baseline [path]
|
|
198
198
|
vibgrate report [--in <artifact.json>] [--format md|text|json]
|
|
199
199
|
vibgrate push [--dsn <dsn>] [--file <artifact.json>] [--strict]
|
|
@@ -225,7 +225,19 @@ Expected result:
|
|
|
225
225
|
- Exit code `2` when the configured gate is exceeded
|
|
226
226
|
|
|
227
227
|
```bash
|
|
228
|
-
# 3)
|
|
228
|
+
# 3) Scan while excluding vendored / generated paths
|
|
229
|
+
# --exclude is repeatable and accepts comma/semicolon-separated globs;
|
|
230
|
+
# patterns are merged with `exclude` from vibgrate.config.*
|
|
231
|
+
npx @vibgrate/cli scan . --exclude "legacy/**,vendor/**" --exclude "**/*.generated.ts"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Expected result:
|
|
235
|
+
|
|
236
|
+
- Matching directories and files are skipped before scanning
|
|
237
|
+
- Excludes from the config file still apply (CLI patterns are additive)
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# 4) Offline scan using local package-version bundle
|
|
229
241
|
npx @vibgrate/cli scan . --offline --package-manifest ./latest-packages.zip --format json --out scan.json
|
|
230
242
|
```
|
|
231
243
|
|
|
@@ -236,7 +248,7 @@ Expected result:
|
|
|
236
248
|
- Package freshness may be marked unknown if manifest lacks entries
|
|
237
249
|
|
|
238
250
|
```bash
|
|
239
|
-
#
|
|
251
|
+
# 5) Export SBOM and compare two runs
|
|
240
252
|
npx @vibgrate/cli sbom export --format cyclonedx --out sbom.cdx.json
|
|
241
253
|
npx @vibgrate/cli sbom delta --from .vibgrate/baseline.json --to .vibgrate/scan_result.json --out sbom-delta.txt
|
|
242
254
|
```
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
baselineCommand,
|
|
3
3
|
runBaseline
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-4CDBCG4I.js";
|
|
5
|
+
import "./chunk-EIZZ6VC3.js";
|
|
6
6
|
import "./chunk-74ZJFYEM.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-C7LU6YIL.js";
|
|
8
8
|
import "./chunk-JSBRDJBE.js";
|
|
9
9
|
export {
|
|
10
10
|
baselineCommand,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runScan
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EIZZ6VC3.js";
|
|
4
4
|
|
|
5
5
|
// src/commands/baseline.ts
|
|
6
6
|
import * as path3 from "path";
|
|
@@ -48,9 +48,12 @@ import * as path from "path";
|
|
|
48
48
|
|
|
49
49
|
// src/utils/fs.ts
|
|
50
50
|
var execFileAsync = promisify(execFile);
|
|
51
|
+
function stripBom(text) {
|
|
52
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
53
|
+
}
|
|
51
54
|
async function readJsonFile(filePath) {
|
|
52
55
|
const txt = await fs.readFile(filePath, "utf8");
|
|
53
|
-
return JSON.parse(txt);
|
|
56
|
+
return JSON.parse(stripBom(txt));
|
|
54
57
|
}
|
|
55
58
|
async function pathExists(p) {
|
|
56
59
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ../vibgrate-core/dist/chunk-
|
|
1
|
+
// ../vibgrate-core/dist/chunk-R3UFC4G6.js
|
|
2
2
|
import { execFile } from "child_process";
|
|
3
3
|
import * as fs from "fs/promises";
|
|
4
4
|
import * as os from "os";
|
|
@@ -32,6 +32,18 @@ var Semaphore = class {
|
|
|
32
32
|
else this.available++;
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
|
+
function parseExcludePatterns(input) {
|
|
36
|
+
if (input === void 0) return [];
|
|
37
|
+
const raw = Array.isArray(input) ? input : [input];
|
|
38
|
+
const out = [];
|
|
39
|
+
for (const entry of raw) {
|
|
40
|
+
for (const part of entry.split(/[,;]/)) {
|
|
41
|
+
const trimmed = part.trim();
|
|
42
|
+
if (trimmed) out.push(trimmed);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return [...new Set(out)];
|
|
46
|
+
}
|
|
35
47
|
function compileGlobs(patterns) {
|
|
36
48
|
if (patterns.length === 0) return null;
|
|
37
49
|
const matchers = patterns.map((p) => compileOne(normalise(p)));
|
|
@@ -112,6 +124,10 @@ function escapeRegex(s) {
|
|
|
112
124
|
var execFileAsync = promisify(execFile);
|
|
113
125
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
114
126
|
"node_modules",
|
|
127
|
+
// Vendored third-party dependency trees (Go vendor/, PHP composer,
|
|
128
|
+
// Rails vendor/) — their manifests are not the repo's own projects and
|
|
129
|
+
// their runtimes/dependencies must not produce drift findings.
|
|
130
|
+
"vendor",
|
|
115
131
|
".git",
|
|
116
132
|
".vibgrate",
|
|
117
133
|
".wrangler",
|
|
@@ -550,7 +566,7 @@ var FileCache = class _FileCache {
|
|
|
550
566
|
if (cached) return cached;
|
|
551
567
|
const promise = this.readTextFile(abs).then((txt) => {
|
|
552
568
|
this.textCache.delete(abs);
|
|
553
|
-
return JSON.parse(txt);
|
|
569
|
+
return JSON.parse(stripBom(txt));
|
|
554
570
|
});
|
|
555
571
|
this.jsonCache.set(abs, promise);
|
|
556
572
|
return promise;
|
|
@@ -745,9 +761,12 @@ async function findSolutionFiles(rootDir) {
|
|
|
745
761
|
async function findCsprojFiles(rootDir) {
|
|
746
762
|
return findFiles(rootDir, (name) => name.endsWith(".csproj"));
|
|
747
763
|
}
|
|
764
|
+
function stripBom(text) {
|
|
765
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
766
|
+
}
|
|
748
767
|
async function readJsonFile(filePath) {
|
|
749
768
|
const txt = await fs.readFile(filePath, "utf8");
|
|
750
|
-
return JSON.parse(txt);
|
|
769
|
+
return JSON.parse(stripBom(txt));
|
|
751
770
|
}
|
|
752
771
|
async function readTextFile(filePath) {
|
|
753
772
|
return fs.readFile(filePath, "utf8");
|
|
@@ -774,6 +793,7 @@ async function writeTextFile(filePath, content) {
|
|
|
774
793
|
|
|
775
794
|
export {
|
|
776
795
|
Semaphore,
|
|
796
|
+
parseExcludePatterns,
|
|
777
797
|
FileCache,
|
|
778
798
|
quickTreeCount,
|
|
779
799
|
normalizeGlobForRipgrep,
|
|
@@ -783,6 +803,7 @@ export {
|
|
|
783
803
|
findPackageJsonFiles,
|
|
784
804
|
findSolutionFiles,
|
|
785
805
|
findCsprojFiles,
|
|
806
|
+
stripBom,
|
|
786
807
|
readJsonFile,
|
|
787
808
|
readTextFile,
|
|
788
809
|
pathExists,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
readTextFile,
|
|
15
15
|
writeJsonFile,
|
|
16
16
|
writeTextFile
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-C7LU6YIL.js";
|
|
18
18
|
import {
|
|
19
19
|
__commonJS,
|
|
20
20
|
__toESM
|
|
@@ -1802,6 +1802,8 @@ import * as path31 from "path";
|
|
|
1802
1802
|
import * as path33 from "path";
|
|
1803
1803
|
import chalk3 from "chalk";
|
|
1804
1804
|
import chalk2 from "chalk";
|
|
1805
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
1806
|
+
import { dirname as dirname182, basename as basename192 } from "path";
|
|
1805
1807
|
import * as fs7 from "fs/promises";
|
|
1806
1808
|
import * as path32 from "path";
|
|
1807
1809
|
var CONFIG_FILES = [
|
|
@@ -4938,7 +4940,7 @@ async function scanPythonProjects(rootDir, pypiCache, cache, projectScanTimeout)
|
|
|
4938
4940
|
return results;
|
|
4939
4941
|
}
|
|
4940
4942
|
async function findPythonManifests(rootDir) {
|
|
4941
|
-
const { findFiles: findFiles2 } = await import("./fs-
|
|
4943
|
+
const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
|
|
4942
4944
|
return findFiles2(rootDir, (name) => PYTHON_MANIFEST_FILES.has(name) || /^requirements.*\.txt$/.test(name));
|
|
4943
4945
|
}
|
|
4944
4946
|
async function scanOnePythonProject(dir, manifestFiles, rootDir, pypiCache, cache) {
|
|
@@ -5335,7 +5337,7 @@ async function scanJavaProjects(rootDir, mavenCache, cache, projectScanTimeout)
|
|
|
5335
5337
|
return results;
|
|
5336
5338
|
}
|
|
5337
5339
|
async function findJavaManifests(rootDir) {
|
|
5338
|
-
const { findFiles: findFiles2 } = await import("./fs-
|
|
5340
|
+
const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
|
|
5339
5341
|
return findFiles2(rootDir, (name) => JAVA_MANIFEST_FILES.has(name));
|
|
5340
5342
|
}
|
|
5341
5343
|
async function scanOneJavaProject(dir, manifestFiles, rootDir, mavenCache, cache) {
|
|
@@ -5700,7 +5702,7 @@ async function scanRubyProjects(rootDir, rubygemsCache, cache, projectScanTimeou
|
|
|
5700
5702
|
return results;
|
|
5701
5703
|
}
|
|
5702
5704
|
async function findRubyManifests(rootDir) {
|
|
5703
|
-
const { findFiles: findFiles2 } = await import("./fs-
|
|
5705
|
+
const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
|
|
5704
5706
|
return findFiles2(rootDir, (name) => RUBY_MANIFEST_FILES.has(name) || isGemspec(name));
|
|
5705
5707
|
}
|
|
5706
5708
|
async function scanOneRubyProject(dir, manifestFiles, rootDir, rubygemsCache, cache) {
|
|
@@ -5924,7 +5926,7 @@ async function scanSwiftProjects(rootDir, swiftCache, cache, projectScanTimeout)
|
|
|
5924
5926
|
return results;
|
|
5925
5927
|
}
|
|
5926
5928
|
async function findSwiftManifests(rootDir) {
|
|
5927
|
-
const { findFiles: findFiles2 } = await import("./fs-
|
|
5929
|
+
const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
|
|
5928
5930
|
return findFiles2(rootDir, (name) => SWIFT_MANIFEST_FILES.has(name));
|
|
5929
5931
|
}
|
|
5930
5932
|
async function scanOneSwiftProject(dir, manifestFile, rootDir, swiftCache, cache) {
|
|
@@ -6161,7 +6163,7 @@ async function scanGoProjects(rootDir, goCache, cache, projectScanTimeout) {
|
|
|
6161
6163
|
return results;
|
|
6162
6164
|
}
|
|
6163
6165
|
async function findGoManifests(rootDir) {
|
|
6164
|
-
const { findFiles: findFiles2 } = await import("./fs-
|
|
6166
|
+
const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
|
|
6165
6167
|
return findFiles2(rootDir, (name) => GO_MANIFEST_FILES.has(name));
|
|
6166
6168
|
}
|
|
6167
6169
|
async function scanOneGoProject(dir, manifestFile, rootDir, goCache, cache) {
|
|
@@ -6395,7 +6397,7 @@ async function scanRustProjects(rootDir, cargoCache, cache, projectScanTimeout)
|
|
|
6395
6397
|
return results;
|
|
6396
6398
|
}
|
|
6397
6399
|
async function findRustManifests(rootDir) {
|
|
6398
|
-
const { findFiles: findFiles2 } = await import("./fs-
|
|
6400
|
+
const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
|
|
6399
6401
|
return findFiles2(rootDir, (name) => RUST_MANIFEST_FILES.has(name));
|
|
6400
6402
|
}
|
|
6401
6403
|
async function scanOneRustProject(dir, manifestFile, rootDir, cargoCache, cache) {
|
|
@@ -6619,7 +6621,7 @@ async function scanPhpProjects(rootDir, composerCache, cache, projectScanTimeout
|
|
|
6619
6621
|
return results;
|
|
6620
6622
|
}
|
|
6621
6623
|
async function findPhpManifests(rootDir) {
|
|
6622
|
-
const { findFiles: findFiles2 } = await import("./fs-
|
|
6624
|
+
const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
|
|
6623
6625
|
return findFiles2(rootDir, (name) => PHP_MANIFEST_FILES.has(name));
|
|
6624
6626
|
}
|
|
6625
6627
|
async function scanOnePhpProject(dir, manifestFile, rootDir, composerCache, cache) {
|
|
@@ -6795,7 +6797,7 @@ function parsePubspecYaml(content) {
|
|
|
6795
6797
|
for (const line of content.split(/\r?\n/)) {
|
|
6796
6798
|
const trimmed = line.trim();
|
|
6797
6799
|
if (trimmed.startsWith("sdk:")) {
|
|
6798
|
-
const match = trimmed.match(/sdk:\s*['"]
|
|
6800
|
+
const match = trimmed.match(/sdk:\s*['"]?>?=?\s*(\d[^<'"]*)/);
|
|
6799
6801
|
if (match) dartVersion = match[1].trim();
|
|
6800
6802
|
continue;
|
|
6801
6803
|
}
|
|
@@ -6805,7 +6807,7 @@ function parsePubspecYaml(content) {
|
|
|
6805
6807
|
} else if (trimmed === "dev_dependencies:") {
|
|
6806
6808
|
currentSection = "dev_dependencies";
|
|
6807
6809
|
continue;
|
|
6808
|
-
} else if (/^\w+:/.test(
|
|
6810
|
+
} else if (/^\w+:/.test(line)) {
|
|
6809
6811
|
currentSection = null;
|
|
6810
6812
|
continue;
|
|
6811
6813
|
}
|
|
@@ -6833,7 +6835,7 @@ async function parsePubspecLock(filePath, cache) {
|
|
|
6833
6835
|
let currentPackage = null;
|
|
6834
6836
|
for (const line of content.split(/\r?\n/)) {
|
|
6835
6837
|
const trimmed = line.trim();
|
|
6836
|
-
const pkgMatch =
|
|
6838
|
+
const pkgMatch = line.match(/^ {2}(\w+):$/);
|
|
6837
6839
|
if (pkgMatch) {
|
|
6838
6840
|
currentPackage = pkgMatch[1];
|
|
6839
6841
|
continue;
|
|
@@ -6884,7 +6886,7 @@ async function scanDartProjects(rootDir, pubCache, cache, projectScanTimeout) {
|
|
|
6884
6886
|
return results;
|
|
6885
6887
|
}
|
|
6886
6888
|
async function findDartManifests(rootDir) {
|
|
6887
|
-
const { findFiles: findFiles2 } = await import("./fs-
|
|
6889
|
+
const { findFiles: findFiles2 } = await import("./fs-PXXYZATK-EW5LCUA7.js");
|
|
6888
6890
|
return findFiles2(rootDir, (name) => DART_MANIFEST_FILES.has(name));
|
|
6889
6891
|
}
|
|
6890
6892
|
async function scanOneDartProject(dir, manifestFile, rootDir, pubCache, cache) {
|
|
@@ -7254,6 +7256,7 @@ async function scanElixirProjects(rootDir, manifest, cache, projectScanTimeout,
|
|
|
7254
7256
|
try {
|
|
7255
7257
|
const scan = await scanElixir(dir, cache, manifest, offline);
|
|
7256
7258
|
if (scan) {
|
|
7259
|
+
scan.path = path13.relative(rootDir, dir) || ".";
|
|
7257
7260
|
results.push(scan);
|
|
7258
7261
|
}
|
|
7259
7262
|
} catch (error) {
|
|
@@ -7511,6 +7514,7 @@ async function scanDockerProjects(rootDir, manifest, cache, projectScanTimeout,
|
|
|
7511
7514
|
try {
|
|
7512
7515
|
const scan = await scanDocker(dir, cache, manifest, offline);
|
|
7513
7516
|
if (scan) {
|
|
7517
|
+
scan.path = path14.relative(rootDir, dir) || ".";
|
|
7514
7518
|
results.push(scan);
|
|
7515
7519
|
}
|
|
7516
7520
|
} catch (error) {
|
|
@@ -7747,6 +7751,7 @@ async function scanHelmProjects(rootDir, manifest, cache, projectScanTimeout, of
|
|
|
7747
7751
|
try {
|
|
7748
7752
|
const scan = await scanHelm(dir, cache, manifest, offline);
|
|
7749
7753
|
if (scan) {
|
|
7754
|
+
scan.path = path15.relative(rootDir, dir) || ".";
|
|
7750
7755
|
results.push(scan);
|
|
7751
7756
|
}
|
|
7752
7757
|
} catch (error) {
|
|
@@ -8123,6 +8128,7 @@ async function scanTerraformProjects(rootDir, manifest, cache, projectScanTimeou
|
|
|
8123
8128
|
try {
|
|
8124
8129
|
const scan = await scanTerraform(dir, cache, manifest, offline);
|
|
8125
8130
|
if (scan) {
|
|
8131
|
+
scan.path = path16.relative(rootDir, dir) || ".";
|
|
8126
8132
|
results.push(scan);
|
|
8127
8133
|
}
|
|
8128
8134
|
} catch (error) {
|
|
@@ -8184,6 +8190,9 @@ function parseLineDependencies(content, regex, capture = 1) {
|
|
|
8184
8190
|
function getProjectName(projectPath, rootDir) {
|
|
8185
8191
|
return path17.basename(projectPath) || path17.basename(rootDir);
|
|
8186
8192
|
}
|
|
8193
|
+
function makefileHasCSignals(content) {
|
|
8194
|
+
return /^\s*(CC|CFLAGS|LDFLAGS)\s*[:?+]?=/m.test(content) || /\b(gcc|clang)\b/.test(content) || /\.(c|o)\b/.test(content);
|
|
8195
|
+
}
|
|
8187
8196
|
function addProject(projects, seen, type, projectPath, rootDir, dependencies = []) {
|
|
8188
8197
|
const normalizedPath = projectPath || ".";
|
|
8189
8198
|
const key = `${type}:${normalizedPath}`;
|
|
@@ -8254,6 +8263,10 @@ async function scanPolyglotProjects(rootDir, cache) {
|
|
|
8254
8263
|
for (const file of candidateFiles) {
|
|
8255
8264
|
const mapping = MANIFEST_TO_LANGUAGE.find((m) => m.name === file.name || m.name.startsWith("*.") && file.name.endsWith(m.name.slice(1)));
|
|
8256
8265
|
if (!mapping) continue;
|
|
8266
|
+
if (file.name === "Makefile") {
|
|
8267
|
+
const text = cache ? await cache.readTextFile(file.absPath) : await readTextFile(file.absPath);
|
|
8268
|
+
if (!makefileHasCSignals(text)) continue;
|
|
8269
|
+
}
|
|
8257
8270
|
const projectPath = path17.dirname(file.relPath) || ".";
|
|
8258
8271
|
const dependencies = await parseDepsByManifest(file.absPath, cache);
|
|
8259
8272
|
addProject(projects, seen, mapping.type, projectPath, rootDir, dependencies);
|
|
@@ -13406,6 +13419,54 @@ async function computeTreeMetadataHash(rootDir, options) {
|
|
|
13406
13419
|
}
|
|
13407
13420
|
return digest.digest("hex");
|
|
13408
13421
|
}
|
|
13422
|
+
var ProgressTrace = class _ProgressTrace {
|
|
13423
|
+
constructor(outPath) {
|
|
13424
|
+
this.outPath = outPath;
|
|
13425
|
+
}
|
|
13426
|
+
events = [];
|
|
13427
|
+
start = Date.now();
|
|
13428
|
+
lastThrottled = /* @__PURE__ */ new Map();
|
|
13429
|
+
flushed = false;
|
|
13430
|
+
/** Returns a trace when VIBGRATE_TRACE_EVENTS is set, else null. */
|
|
13431
|
+
static fromEnv() {
|
|
13432
|
+
const path34 = process.env.VIBGRATE_TRACE_EVENTS;
|
|
13433
|
+
return path34 ? new _ProgressTrace(path34) : null;
|
|
13434
|
+
}
|
|
13435
|
+
record(op, data = {}) {
|
|
13436
|
+
if (this.flushed) return;
|
|
13437
|
+
this.events.push({ t: Date.now() - this.start, op, ...data });
|
|
13438
|
+
}
|
|
13439
|
+
/**
|
|
13440
|
+
* Record at most one event per key per interval. Used for high-frequency
|
|
13441
|
+
* updates (sub-step progress, live stats) so traces stay compact.
|
|
13442
|
+
*/
|
|
13443
|
+
recordThrottled(key, op, data = {}, intervalMs = 120) {
|
|
13444
|
+
if (this.flushed) return;
|
|
13445
|
+
const now = Date.now();
|
|
13446
|
+
const last = this.lastThrottled.get(key) ?? 0;
|
|
13447
|
+
if (now - last < intervalMs) return;
|
|
13448
|
+
this.lastThrottled.set(key, now);
|
|
13449
|
+
this.record(op, data);
|
|
13450
|
+
}
|
|
13451
|
+
/** Write the trace document. Safe to call once; later calls are no-ops. */
|
|
13452
|
+
flush(meta) {
|
|
13453
|
+
if (this.flushed) return;
|
|
13454
|
+
this.flushed = true;
|
|
13455
|
+
const doc = {
|
|
13456
|
+
traceVersion: 1,
|
|
13457
|
+
cliVersion: meta.cliVersion,
|
|
13458
|
+
workspace: basename192(meta.rootDir) || meta.rootDir,
|
|
13459
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13460
|
+
durationMs: Date.now() - this.start,
|
|
13461
|
+
events: this.events
|
|
13462
|
+
};
|
|
13463
|
+
try {
|
|
13464
|
+
mkdirSync(dirname182(this.outPath), { recursive: true });
|
|
13465
|
+
writeFileSync(this.outPath, JSON.stringify(doc));
|
|
13466
|
+
} catch {
|
|
13467
|
+
}
|
|
13468
|
+
}
|
|
13469
|
+
};
|
|
13409
13470
|
var ROBOT = [
|
|
13410
13471
|
chalk2.cyan(" \u256D\u2500\u2500\u2500\u256E") + chalk2.greenBright("\u279C"),
|
|
13411
13472
|
chalk2.cyan(" \u256D\u2524") + chalk2.greenBright("\u25C9 \u25C9") + chalk2.cyan("\u251C\u256E"),
|
|
@@ -13449,8 +13510,11 @@ var ScanProgress = class {
|
|
|
13449
13510
|
/** Last emitted step snapshot for append-only output modes */
|
|
13450
13511
|
lastLoggedStates = /* @__PURE__ */ new Map();
|
|
13451
13512
|
version;
|
|
13513
|
+
/** Optional semantic event recorder (VIBGRATE_TRACE_EVENTS) for the web simulator */
|
|
13514
|
+
trace;
|
|
13452
13515
|
constructor(rootDir, version = "unknown") {
|
|
13453
13516
|
this.version = version;
|
|
13517
|
+
this.trace = ProgressTrace.fromEnv();
|
|
13454
13518
|
this.isTTY = process.stderr.isTTY ?? false;
|
|
13455
13519
|
this.useLiveUpdates = this.isTTY && process.env.VIBGRATE_PROGRESS_MODE !== "plain";
|
|
13456
13520
|
this.rootDir = rootDir;
|
|
@@ -13475,6 +13539,7 @@ var ScanProgress = class {
|
|
|
13475
13539
|
/** Set the estimated total duration from scan history */
|
|
13476
13540
|
setEstimatedTotal(estimatedMs) {
|
|
13477
13541
|
this.estimatedTotalMs = estimatedMs;
|
|
13542
|
+
this.trace?.record("setEstimatedTotal", { estimatedMs });
|
|
13478
13543
|
}
|
|
13479
13544
|
/** Set per-step estimated durations from scan history */
|
|
13480
13545
|
setStepEstimates(estimates) {
|
|
@@ -13487,6 +13552,7 @@ var ScanProgress = class {
|
|
|
13487
13552
|
/** Register all steps up front, optionally with weights */
|
|
13488
13553
|
setSteps(steps) {
|
|
13489
13554
|
this.steps = steps.map((s) => ({ ...s, status: "pending", weight: s.weight ?? 1 }));
|
|
13555
|
+
this.trace?.record("setSteps", { steps: steps.map((s) => ({ id: s.id, label: s.label, weight: s.weight ?? 1 })) });
|
|
13490
13556
|
if (this.isTTY) {
|
|
13491
13557
|
const header = [
|
|
13492
13558
|
"",
|
|
@@ -13512,6 +13578,7 @@ var ScanProgress = class {
|
|
|
13512
13578
|
} else {
|
|
13513
13579
|
this.steps.push(newStep);
|
|
13514
13580
|
}
|
|
13581
|
+
this.trace?.record("insertStepBefore", { beforeId, step: { id: step.id, label: step.label, weight: step.weight ?? 1 } });
|
|
13515
13582
|
}
|
|
13516
13583
|
/** Mark a step as active (currently running), optionally with expected total */
|
|
13517
13584
|
startStep(id, subTotal) {
|
|
@@ -13524,6 +13591,7 @@ var ScanProgress = class {
|
|
|
13524
13591
|
step.subTotal = subTotal;
|
|
13525
13592
|
}
|
|
13526
13593
|
this.stepStartTimes.set(id, Date.now());
|
|
13594
|
+
this.trace?.record("startStep", { id, subTotal });
|
|
13527
13595
|
this.render();
|
|
13528
13596
|
}
|
|
13529
13597
|
/** Mark a step as completed */
|
|
@@ -13538,6 +13606,7 @@ var ScanProgress = class {
|
|
|
13538
13606
|
if (started) {
|
|
13539
13607
|
this.stepTimings.push({ id, durationMs: Date.now() - started });
|
|
13540
13608
|
}
|
|
13609
|
+
this.trace?.record("completeStep", { id, detail, count });
|
|
13541
13610
|
this.render();
|
|
13542
13611
|
}
|
|
13543
13612
|
/** Mark a step as skipped */
|
|
@@ -13547,6 +13616,7 @@ var ScanProgress = class {
|
|
|
13547
13616
|
step.status = "skipped";
|
|
13548
13617
|
step.detail = "disabled";
|
|
13549
13618
|
}
|
|
13619
|
+
this.trace?.record("skipStep", { id });
|
|
13550
13620
|
this.render();
|
|
13551
13621
|
}
|
|
13552
13622
|
/** Update sub-step progress for the active step (files processed, etc.) */
|
|
@@ -13557,32 +13627,51 @@ var ScanProgress = class {
|
|
|
13557
13627
|
if (total !== void 0) step.subTotal = total;
|
|
13558
13628
|
if (label !== void 0) step.subLabel = label;
|
|
13559
13629
|
}
|
|
13630
|
+
this.trace?.recordThrottled(`sub:${id}`, "updateStepProgress", { id, current, total, label });
|
|
13560
13631
|
this.render();
|
|
13561
13632
|
}
|
|
13562
13633
|
/** Update live stats */
|
|
13563
13634
|
updateStats(partial) {
|
|
13564
13635
|
Object.assign(this.stats, partial);
|
|
13636
|
+
this.traceStats(true);
|
|
13565
13637
|
this.render();
|
|
13566
13638
|
}
|
|
13567
13639
|
/** Increment stats */
|
|
13568
13640
|
addProjects(n) {
|
|
13569
13641
|
this.stats.projects += n;
|
|
13642
|
+
this.traceStats();
|
|
13570
13643
|
this.render();
|
|
13571
13644
|
}
|
|
13572
13645
|
addDependencies(n) {
|
|
13573
13646
|
this.stats.dependencies += n;
|
|
13647
|
+
this.traceStats();
|
|
13574
13648
|
this.render();
|
|
13575
13649
|
}
|
|
13576
13650
|
addFrameworks(n) {
|
|
13577
13651
|
this.stats.frameworks += n;
|
|
13652
|
+
this.traceStats();
|
|
13578
13653
|
this.render();
|
|
13579
13654
|
}
|
|
13580
13655
|
addFindings(warnings, errors, notes) {
|
|
13581
13656
|
this.stats.findings.warnings += warnings;
|
|
13582
13657
|
this.stats.findings.errors += errors;
|
|
13583
13658
|
this.stats.findings.notes += notes;
|
|
13659
|
+
this.traceStats();
|
|
13584
13660
|
this.render();
|
|
13585
13661
|
}
|
|
13662
|
+
/** Record a (throttled) snapshot of live stats into the trace */
|
|
13663
|
+
traceStats(force = false) {
|
|
13664
|
+
if (!this.trace) return;
|
|
13665
|
+
const snapshot = {
|
|
13666
|
+
stats: {
|
|
13667
|
+
...this.stats,
|
|
13668
|
+
findings: { ...this.stats.findings },
|
|
13669
|
+
treeSummary: this.stats.treeSummary ? { ...this.stats.treeSummary } : void 0
|
|
13670
|
+
}
|
|
13671
|
+
};
|
|
13672
|
+
if (force) this.trace.record("stats", snapshot);
|
|
13673
|
+
else this.trace.recordThrottled("stats", "stats", snapshot);
|
|
13674
|
+
}
|
|
13586
13675
|
/** Stop the progress display and clear it */
|
|
13587
13676
|
finish() {
|
|
13588
13677
|
if (this.timer) {
|
|
@@ -13607,6 +13696,8 @@ var ScanProgress = class {
|
|
|
13607
13696
|
|
|
13608
13697
|
`)
|
|
13609
13698
|
);
|
|
13699
|
+
this.trace?.record("finish", { doneCount, elapsedMs: Date.now() - this.startTime, summary: `${doneCount} scanners completed in ${elapsed}` });
|
|
13700
|
+
this.trace?.flush({ cliVersion: this.version, rootDir: this.rootDir });
|
|
13610
13701
|
if (this.isTTY && process.platform === "win32") {
|
|
13611
13702
|
process.stdout.write("\x1B[0G\x1B[K");
|
|
13612
13703
|
}
|
|
@@ -13980,7 +14071,7 @@ async function runScan(rootDir, opts) {
|
|
|
13980
14071
|
const composerCache = new ComposerCache(sem, packageManifest, offlineMode);
|
|
13981
14072
|
const pubCache = new PubCache(sem, packageManifest, offlineMode);
|
|
13982
14073
|
const fileCache = new FileCache();
|
|
13983
|
-
const excludePatterns = config.exclude ?? [];
|
|
14074
|
+
const excludePatterns = [.../* @__PURE__ */ new Set([...config.exclude ?? [], ...opts.exclude ?? []])];
|
|
13984
14075
|
fileCache.setExcludePatterns(excludePatterns);
|
|
13985
14076
|
const projectScanTimeoutMs = (opts.projectScanTimeout ?? config.projectScanTimeout ?? 180) * 1e3;
|
|
13986
14077
|
fileCache.setMaxFileSize(config.maxFileSizeToScan ?? 5242880);
|
|
@@ -14260,14 +14351,17 @@ async function runScan(rootDir, opts) {
|
|
|
14260
14351
|
progress.addProjects(polyglotProjects.length);
|
|
14261
14352
|
progress.completeStep("polyglot", `${polyglotProjects.length} project${polyglotProjects.length !== 1 ? "s" : ""}`, polyglotProjects.length);
|
|
14262
14353
|
}
|
|
14354
|
+
const OVERLAY_PROJECT_TYPES = /* @__PURE__ */ new Set(["docker", "helm", "terraform"]);
|
|
14355
|
+
const dedupeKey = (p) => OVERLAY_PROJECT_TYPES.has(p.type) ? `${p.type}:${p.path}` : p.path;
|
|
14263
14356
|
const rawProjects = [...nodeProjects, ...dotnetProjects, ...pythonProjects, ...javaProjects, ...rubyProjects, ...swiftProjects, ...goProjects, ...rustProjects, ...phpProjects, ...dartProjects, ...elixirProjects, ...dockerProjects, ...helmProjects, ...terraformProjects, ...polyglotProjects];
|
|
14264
14357
|
const deduplicatedMap = /* @__PURE__ */ new Map();
|
|
14265
14358
|
for (const project of rawProjects) {
|
|
14266
|
-
const existing = deduplicatedMap.get(project
|
|
14359
|
+
const existing = deduplicatedMap.get(dedupeKey(project));
|
|
14267
14360
|
if (!existing) {
|
|
14268
|
-
deduplicatedMap.set(project
|
|
14361
|
+
deduplicatedMap.set(dedupeKey(project), project);
|
|
14269
14362
|
} else {
|
|
14270
|
-
const
|
|
14363
|
+
const resolvedCount = (p) => p.dependencies.filter((d) => d.resolvedVersion || d.latestStable).length;
|
|
14364
|
+
const keepNew = resolvedCount(project) > resolvedCount(existing) || resolvedCount(project) === resolvedCount(existing) && project.dependencies.length > existing.dependencies.length;
|
|
14271
14365
|
const winner = keepNew ? project : existing;
|
|
14272
14366
|
const loser = keepNew ? existing : project;
|
|
14273
14367
|
if (loser.projectReferences?.length) {
|
|
@@ -14280,7 +14374,7 @@ async function runScan(rootDir, opts) {
|
|
|
14280
14374
|
}
|
|
14281
14375
|
}
|
|
14282
14376
|
}
|
|
14283
|
-
deduplicatedMap.set(project
|
|
14377
|
+
deduplicatedMap.set(dedupeKey(project), winner);
|
|
14284
14378
|
}
|
|
14285
14379
|
}
|
|
14286
14380
|
const allProjects = [...deduplicatedMap.values()];
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
pathExists,
|
|
7
7
|
readJsonFile,
|
|
8
8
|
writeTextFile
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-4CDBCG4I.js";
|
|
10
10
|
import {
|
|
11
11
|
computeRepoFingerprint,
|
|
12
12
|
detectVcs,
|
|
@@ -16,13 +16,14 @@ import {
|
|
|
16
16
|
resolveRepositoryName,
|
|
17
17
|
runScan,
|
|
18
18
|
writeDefaultConfig
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-EIZZ6VC3.js";
|
|
20
20
|
import {
|
|
21
21
|
require_semver
|
|
22
22
|
} from "./chunk-74ZJFYEM.js";
|
|
23
23
|
import {
|
|
24
|
+
parseExcludePatterns,
|
|
24
25
|
pathExists as pathExists2
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-C7LU6YIL.js";
|
|
26
27
|
import {
|
|
27
28
|
__toESM
|
|
28
29
|
} from "./chunk-JSBRDJBE.js";
|
|
@@ -48,7 +49,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
48
49
|
console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
|
|
49
50
|
}
|
|
50
51
|
if (opts.baseline) {
|
|
51
|
-
const { runBaseline } = await import("./baseline-
|
|
52
|
+
const { runBaseline } = await import("./baseline-QRGGTCJD.js");
|
|
52
53
|
await runBaseline(rootDir);
|
|
53
54
|
}
|
|
54
55
|
console.log("");
|
|
@@ -302,6 +303,9 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
302
303
|
if (opts.strict) process.exit(1);
|
|
303
304
|
}
|
|
304
305
|
}
|
|
306
|
+
function collectExcludes(value, previous) {
|
|
307
|
+
return [...previous, ...parseExcludePatterns(value)];
|
|
308
|
+
}
|
|
305
309
|
function parseNonNegativeNumber(value, label) {
|
|
306
310
|
if (value === void 0) return void 0;
|
|
307
311
|
const parsed = Number(value);
|
|
@@ -310,7 +314,12 @@ function parseNonNegativeNumber(value, label) {
|
|
|
310
314
|
}
|
|
311
315
|
return parsed;
|
|
312
316
|
}
|
|
313
|
-
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|md)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").option(
|
|
317
|
+
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|md)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").option(
|
|
318
|
+
"-e, --exclude <glob>",
|
|
319
|
+
'Exclude paths matching a glob pattern. Repeatable, and a single value may list several patterns separated by commas or semicolons (e.g. --exclude "legacy/**,vendor/**"). Merged with excludes from the config file.',
|
|
320
|
+
collectExcludes,
|
|
321
|
+
[]
|
|
322
|
+
).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").option("--ui-purpose", "Enable optional UI purpose evidence extraction (slower)").option("--no-local-artifacts", "Do not write .vibgrate JSON artifacts to disk").option("--max-privacy", "Enable strongest privacy mode (minimal scanners, no local artifacts)").option("--offline", "Run without network calls; do not upload results").option("--package-manifest <file>", "Use local package-version manifest JSON/ZIP (for offline mode)").option("--project-scan-timeout <seconds>", "Per-project scan timeout in seconds (default: 180)").option("--drift-budget <score>", "Fail if drift score is above budget (0-100)").option("--drift-worsening <percent>", "Fail if drift worsens by more than % since baseline").action(async (targetPath, opts) => {
|
|
314
323
|
const rootDir = path3.resolve(targetPath);
|
|
315
324
|
if (!await pathExists2(rootDir)) {
|
|
316
325
|
console.error(chalk3.red(`Path does not exist: ${rootDir}`));
|
|
@@ -374,6 +383,7 @@ var scanCommand = new Command3("scan").description("Scan a project for upgrade d
|
|
|
374
383
|
failOn: opts.failOn,
|
|
375
384
|
baseline: opts.baseline,
|
|
376
385
|
changedOnly: opts.changedOnly,
|
|
386
|
+
exclude: opts.exclude,
|
|
377
387
|
concurrency: parseInt(opts.concurrency, 10) || 8,
|
|
378
388
|
push: opts.push,
|
|
379
389
|
dsn: opts.dsn,
|