@vibgrate/cli 1.0.47 → 1.0.49
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.
|
@@ -324,8 +324,13 @@ function formatText(artifact) {
|
|
|
324
324
|
lines.push(chalk.bold.cyan("\u2551 Project Relationship Diagram \u2551"));
|
|
325
325
|
lines.push(chalk.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
326
326
|
lines.push("");
|
|
327
|
-
lines.push(chalk.bold(" Mermaid"));
|
|
328
|
-
lines.push(
|
|
327
|
+
lines.push(chalk.bold(" Mermaid") + chalk.dim(" (copy into https://mermaid.live or a ```mermaid code block)"));
|
|
328
|
+
lines.push("");
|
|
329
|
+
lines.push(chalk.dim(" ```mermaid"));
|
|
330
|
+
for (const mLine of artifact.relationshipDiagram.mermaid.split("\n")) {
|
|
331
|
+
lines.push(chalk.dim(` ${mLine}`));
|
|
332
|
+
}
|
|
333
|
+
lines.push(chalk.dim(" ```"));
|
|
329
334
|
lines.push("");
|
|
330
335
|
}
|
|
331
336
|
if (artifact.solutions && artifact.solutions.length > 0) {
|
|
@@ -1100,7 +1105,7 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
|
|
|
1100
1105
|
});
|
|
1101
1106
|
|
|
1102
1107
|
// src/commands/scan.ts
|
|
1103
|
-
import * as
|
|
1108
|
+
import * as path22 from "path";
|
|
1104
1109
|
import { Command as Command3 } from "commander";
|
|
1105
1110
|
import chalk6 from "chalk";
|
|
1106
1111
|
|
|
@@ -2740,6 +2745,149 @@ async function scanOneJavaProject(dir, manifestFiles, rootDir, mavenCache, cache
|
|
|
2740
2745
|
};
|
|
2741
2746
|
}
|
|
2742
2747
|
|
|
2748
|
+
// src/scanners/polyglot-scanner.ts
|
|
2749
|
+
import * as path8 from "path";
|
|
2750
|
+
var MANIFEST_TO_LANGUAGE = [
|
|
2751
|
+
{ name: "go.mod", type: "go" },
|
|
2752
|
+
{ name: "Cargo.toml", type: "rust" },
|
|
2753
|
+
{ name: "composer.json", type: "php" },
|
|
2754
|
+
{ name: "Gemfile", type: "ruby" },
|
|
2755
|
+
{ name: "Package.swift", type: "swift" },
|
|
2756
|
+
{ name: "pubspec.yaml", type: "dart" },
|
|
2757
|
+
{ name: "build.gradle.kts", type: "kotlin" },
|
|
2758
|
+
{ name: "build.sbt", type: "scala" },
|
|
2759
|
+
{ name: "DESCRIPTION", type: "r" },
|
|
2760
|
+
{ name: "Podfile", type: "objective-c" },
|
|
2761
|
+
{ name: "mix.exs", type: "elixir" },
|
|
2762
|
+
{ name: "cpanfile", type: "perl" },
|
|
2763
|
+
{ name: "Project.toml", type: "julia" },
|
|
2764
|
+
{ name: "deps.edn", type: "clojure" },
|
|
2765
|
+
{ name: "build.gradle", type: "groovy" },
|
|
2766
|
+
{ name: "tsconfig.json", type: "typescript" },
|
|
2767
|
+
{ name: "Makefile", type: "c" },
|
|
2768
|
+
{ name: "CMakeLists.txt", type: "cpp" },
|
|
2769
|
+
{ name: "*.vbp", type: "visual-basic" }
|
|
2770
|
+
];
|
|
2771
|
+
var EXTENSION_TO_LANGUAGE = [
|
|
2772
|
+
{ extensions: [".m", ".mm"], type: "objective-c" },
|
|
2773
|
+
{ extensions: [".c", ".h"], type: "c" },
|
|
2774
|
+
{ extensions: [".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx"], type: "cpp" },
|
|
2775
|
+
{ extensions: [".cob", ".cbl", ".cpy"], type: "cobol" },
|
|
2776
|
+
{ extensions: [".f", ".for", ".f90", ".f95", ".f03", ".f08"], type: "fortran" },
|
|
2777
|
+
{ extensions: [".pas", ".pp", ".lpr"], type: "pascal" },
|
|
2778
|
+
{ extensions: [".adb", ".ads", ".ada"], type: "ada" },
|
|
2779
|
+
{ extensions: [".asm", ".s", ".s43", ".s65"], type: "assembly" },
|
|
2780
|
+
{ extensions: [".rpg", ".rpgle", ".sqlrpgle"], type: "rpg" }
|
|
2781
|
+
];
|
|
2782
|
+
function makeDep(pkg2, spec = "unknown") {
|
|
2783
|
+
return {
|
|
2784
|
+
package: pkg2,
|
|
2785
|
+
section: "dependencies",
|
|
2786
|
+
currentSpec: spec,
|
|
2787
|
+
resolvedVersion: null,
|
|
2788
|
+
latestStable: null,
|
|
2789
|
+
majorsBehind: null,
|
|
2790
|
+
drift: "unknown"
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
function parseLineDependencies(content, regex, capture = 1) {
|
|
2794
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2795
|
+
for (const line of content.split(/\r?\n/)) {
|
|
2796
|
+
const match = line.match(regex);
|
|
2797
|
+
if (match?.[capture]) deps.add(match[capture]);
|
|
2798
|
+
}
|
|
2799
|
+
return [...deps];
|
|
2800
|
+
}
|
|
2801
|
+
function getProjectName(projectPath, rootDir) {
|
|
2802
|
+
return path8.basename(projectPath) || path8.basename(rootDir);
|
|
2803
|
+
}
|
|
2804
|
+
function addProject(projects, seen, type, projectPath, rootDir, dependencies = []) {
|
|
2805
|
+
const normalizedPath = projectPath || ".";
|
|
2806
|
+
const key = `${type}:${normalizedPath}`;
|
|
2807
|
+
if (seen.has(key)) return;
|
|
2808
|
+
seen.add(key);
|
|
2809
|
+
projects.push({
|
|
2810
|
+
type,
|
|
2811
|
+
path: normalizedPath,
|
|
2812
|
+
name: getProjectName(normalizedPath, rootDir),
|
|
2813
|
+
frameworks: [],
|
|
2814
|
+
dependencies,
|
|
2815
|
+
dependencyAgeBuckets: {
|
|
2816
|
+
current: 0,
|
|
2817
|
+
oneBehind: 0,
|
|
2818
|
+
twoPlusBehind: 0,
|
|
2819
|
+
unknown: dependencies.length
|
|
2820
|
+
}
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
async function parseDepsByManifest(manifestPath, cache) {
|
|
2824
|
+
const filename = path8.basename(manifestPath);
|
|
2825
|
+
const readText = async () => cache ? cache.readTextFile(manifestPath) : readTextFile(manifestPath);
|
|
2826
|
+
if (filename === "composer.json") {
|
|
2827
|
+
const content = cache ? await cache.readJsonFile(manifestPath) : await readJsonFile(manifestPath);
|
|
2828
|
+
if (!content || typeof content !== "object") return [];
|
|
2829
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2830
|
+
for (const key of ["require", "require-dev"]) {
|
|
2831
|
+
const section = content[key];
|
|
2832
|
+
if (section && typeof section === "object") {
|
|
2833
|
+
for (const dep of Object.keys(section)) deps.add(dep);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
return [...deps].map((dep) => makeDep(dep));
|
|
2837
|
+
}
|
|
2838
|
+
const text = await readText();
|
|
2839
|
+
if (!text) return [];
|
|
2840
|
+
if (filename === "go.mod") return parseLineDependencies(text, /^\s*require\s+([^\s]+)\s+(.+)$/).map((dep) => makeDep(dep));
|
|
2841
|
+
if (filename === "Gemfile") return parseLineDependencies(text, /^\s*gem\s+['"]([^'"]+)['"]/).map((dep) => makeDep(dep));
|
|
2842
|
+
if (filename === "Cargo.toml") return parseLineDependencies(text, /^\s*([A-Za-z0-9_\-]+)\s*=\s*['"{]/).map((dep) => makeDep(dep));
|
|
2843
|
+
if (filename === "pubspec.yaml") return parseLineDependencies(text, /^\s{2,}([A-Za-z0-9_\-]+):\s*.+$/).map((dep) => makeDep(dep));
|
|
2844
|
+
if (filename === "mix.exs") return parseLineDependencies(text, /\{\s*:([a-zA-Z0-9_]+),/).map((dep) => makeDep(dep));
|
|
2845
|
+
if (filename === "cpanfile") return parseLineDependencies(text, /^\s*requires\s+['"]([^'"]+)['"]/).map((dep) => makeDep(dep));
|
|
2846
|
+
if (filename === "build.sbt") return parseLineDependencies(text, /"([A-Za-z0-9_.\-]+)"\s*%{1,2}\s*"([A-Za-z0-9_.\-]+)"/, 2).map((dep) => makeDep(dep));
|
|
2847
|
+
return [];
|
|
2848
|
+
}
|
|
2849
|
+
function detectExtensionBackedProjects(entries) {
|
|
2850
|
+
const byDir = /* @__PURE__ */ new Map();
|
|
2851
|
+
for (const entry of entries) {
|
|
2852
|
+
if (!entry.isFile) continue;
|
|
2853
|
+
const ext = path8.extname(entry.name).toLowerCase();
|
|
2854
|
+
if (!ext) continue;
|
|
2855
|
+
for (const mapping of EXTENSION_TO_LANGUAGE) {
|
|
2856
|
+
if (!mapping.extensions.includes(ext)) continue;
|
|
2857
|
+
const dir = path8.dirname(entry.relPath) || ".";
|
|
2858
|
+
if (!byDir.has(dir)) byDir.set(dir, /* @__PURE__ */ new Set());
|
|
2859
|
+
byDir.get(dir)?.add(mapping.type);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
return byDir;
|
|
2863
|
+
}
|
|
2864
|
+
async function scanPolyglotProjects(rootDir, cache) {
|
|
2865
|
+
const entries = cache ? await cache.walkDir(rootDir) : [];
|
|
2866
|
+
const candidateFiles = entries.filter((entry) => entry.isFile && MANIFEST_TO_LANGUAGE.some((m) => m.name === entry.name || m.name.startsWith("*.") && entry.name.endsWith(m.name.slice(1))));
|
|
2867
|
+
const shellDirs = new Set(
|
|
2868
|
+
entries.filter((entry) => entry.isFile && entry.name.endsWith(".sh")).map((entry) => path8.dirname(entry.relPath) || ".")
|
|
2869
|
+
);
|
|
2870
|
+
const projects = [];
|
|
2871
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2872
|
+
for (const file of candidateFiles) {
|
|
2873
|
+
const mapping = MANIFEST_TO_LANGUAGE.find((m) => m.name === file.name || m.name.startsWith("*.") && file.name.endsWith(m.name.slice(1)));
|
|
2874
|
+
if (!mapping) continue;
|
|
2875
|
+
const projectPath = path8.dirname(file.relPath) || ".";
|
|
2876
|
+
const dependencies = await parseDepsByManifest(file.absPath, cache);
|
|
2877
|
+
addProject(projects, seen, mapping.type, projectPath, rootDir, dependencies);
|
|
2878
|
+
}
|
|
2879
|
+
for (const dir of shellDirs) {
|
|
2880
|
+
addProject(projects, seen, "shell", dir, rootDir);
|
|
2881
|
+
}
|
|
2882
|
+
const extensionProjects = detectExtensionBackedProjects(entries);
|
|
2883
|
+
for (const [dir, types] of extensionProjects) {
|
|
2884
|
+
for (const type of types) {
|
|
2885
|
+
addProject(projects, seen, type, dir, rootDir);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
return projects;
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2743
2891
|
// src/scanners/nuget-cache.ts
|
|
2744
2892
|
import * as semver6 from "semver";
|
|
2745
2893
|
var NuGetCache = class {
|
|
@@ -2956,7 +3104,7 @@ var MavenCache = class {
|
|
|
2956
3104
|
};
|
|
2957
3105
|
|
|
2958
3106
|
// src/config.ts
|
|
2959
|
-
import * as
|
|
3107
|
+
import * as path9 from "path";
|
|
2960
3108
|
import * as fs from "fs/promises";
|
|
2961
3109
|
var CONFIG_FILES = [
|
|
2962
3110
|
"vibgrate.config.ts",
|
|
@@ -2982,7 +3130,7 @@ var DEFAULT_CONFIG = {
|
|
|
2982
3130
|
async function loadConfig(rootDir) {
|
|
2983
3131
|
let config = DEFAULT_CONFIG;
|
|
2984
3132
|
for (const file of CONFIG_FILES) {
|
|
2985
|
-
const configPath =
|
|
3133
|
+
const configPath = path9.join(rootDir, file);
|
|
2986
3134
|
if (await pathExists(configPath)) {
|
|
2987
3135
|
if (file.endsWith(".json")) {
|
|
2988
3136
|
const txt = await readTextFile(configPath);
|
|
@@ -2997,7 +3145,7 @@ async function loadConfig(rootDir) {
|
|
|
2997
3145
|
}
|
|
2998
3146
|
}
|
|
2999
3147
|
}
|
|
3000
|
-
const sidecarPath =
|
|
3148
|
+
const sidecarPath = path9.join(rootDir, ".vibgrate", "auto-excludes.json");
|
|
3001
3149
|
if (await pathExists(sidecarPath)) {
|
|
3002
3150
|
try {
|
|
3003
3151
|
const txt = await readTextFile(sidecarPath);
|
|
@@ -3012,7 +3160,7 @@ async function loadConfig(rootDir) {
|
|
|
3012
3160
|
return config;
|
|
3013
3161
|
}
|
|
3014
3162
|
async function writeDefaultConfig(rootDir) {
|
|
3015
|
-
const configPath =
|
|
3163
|
+
const configPath = path9.join(rootDir, "vibgrate.config.ts");
|
|
3016
3164
|
const content = `import type { VibgrateConfig } from '@vibgrate/cli';
|
|
3017
3165
|
|
|
3018
3166
|
const config: VibgrateConfig = {
|
|
@@ -3038,7 +3186,7 @@ export default config;
|
|
|
3038
3186
|
}
|
|
3039
3187
|
async function appendExcludePatterns(rootDir, newPatterns) {
|
|
3040
3188
|
if (newPatterns.length === 0) return false;
|
|
3041
|
-
const jsonPath =
|
|
3189
|
+
const jsonPath = path9.join(rootDir, "vibgrate.config.json");
|
|
3042
3190
|
if (await pathExists(jsonPath)) {
|
|
3043
3191
|
try {
|
|
3044
3192
|
const txt = await readTextFile(jsonPath);
|
|
@@ -3051,8 +3199,8 @@ async function appendExcludePatterns(rootDir, newPatterns) {
|
|
|
3051
3199
|
} catch {
|
|
3052
3200
|
}
|
|
3053
3201
|
}
|
|
3054
|
-
const vibgrateDir =
|
|
3055
|
-
const sidecarPath =
|
|
3202
|
+
const vibgrateDir = path9.join(rootDir, ".vibgrate");
|
|
3203
|
+
const sidecarPath = path9.join(vibgrateDir, "auto-excludes.json");
|
|
3056
3204
|
let existing = [];
|
|
3057
3205
|
if (await pathExists(sidecarPath)) {
|
|
3058
3206
|
try {
|
|
@@ -3073,7 +3221,7 @@ async function appendExcludePatterns(rootDir, newPatterns) {
|
|
|
3073
3221
|
}
|
|
3074
3222
|
|
|
3075
3223
|
// src/utils/vcs.ts
|
|
3076
|
-
import * as
|
|
3224
|
+
import * as path10 from "path";
|
|
3077
3225
|
import * as fs2 from "fs/promises";
|
|
3078
3226
|
async function detectVcs(rootDir) {
|
|
3079
3227
|
try {
|
|
@@ -3087,7 +3235,7 @@ async function detectGit(rootDir) {
|
|
|
3087
3235
|
if (!gitDir) {
|
|
3088
3236
|
return { type: "unknown" };
|
|
3089
3237
|
}
|
|
3090
|
-
const headPath =
|
|
3238
|
+
const headPath = path10.join(gitDir, "HEAD");
|
|
3091
3239
|
let headContent;
|
|
3092
3240
|
try {
|
|
3093
3241
|
headContent = (await fs2.readFile(headPath, "utf8")).trim();
|
|
@@ -3113,10 +3261,10 @@ async function detectGit(rootDir) {
|
|
|
3113
3261
|
};
|
|
3114
3262
|
}
|
|
3115
3263
|
async function findGitDir(startDir) {
|
|
3116
|
-
let dir =
|
|
3117
|
-
const root =
|
|
3264
|
+
let dir = path10.resolve(startDir);
|
|
3265
|
+
const root = path10.parse(dir).root;
|
|
3118
3266
|
while (dir !== root) {
|
|
3119
|
-
const gitPath =
|
|
3267
|
+
const gitPath = path10.join(dir, ".git");
|
|
3120
3268
|
try {
|
|
3121
3269
|
const stat3 = await fs2.stat(gitPath);
|
|
3122
3270
|
if (stat3.isDirectory()) {
|
|
@@ -3125,18 +3273,18 @@ async function findGitDir(startDir) {
|
|
|
3125
3273
|
if (stat3.isFile()) {
|
|
3126
3274
|
const content = (await fs2.readFile(gitPath, "utf8")).trim();
|
|
3127
3275
|
if (content.startsWith("gitdir: ")) {
|
|
3128
|
-
const resolved =
|
|
3276
|
+
const resolved = path10.resolve(dir, content.slice(8));
|
|
3129
3277
|
return resolved;
|
|
3130
3278
|
}
|
|
3131
3279
|
}
|
|
3132
3280
|
} catch {
|
|
3133
3281
|
}
|
|
3134
|
-
dir =
|
|
3282
|
+
dir = path10.dirname(dir);
|
|
3135
3283
|
}
|
|
3136
3284
|
return null;
|
|
3137
3285
|
}
|
|
3138
3286
|
async function resolveRef(gitDir, refPath) {
|
|
3139
|
-
const loosePath =
|
|
3287
|
+
const loosePath = path10.join(gitDir, refPath);
|
|
3140
3288
|
try {
|
|
3141
3289
|
const sha = (await fs2.readFile(loosePath, "utf8")).trim();
|
|
3142
3290
|
if (/^[0-9a-f]{40}$/i.test(sha)) {
|
|
@@ -3144,7 +3292,7 @@ async function resolveRef(gitDir, refPath) {
|
|
|
3144
3292
|
}
|
|
3145
3293
|
} catch {
|
|
3146
3294
|
}
|
|
3147
|
-
const packedPath =
|
|
3295
|
+
const packedPath = path10.join(gitDir, "packed-refs");
|
|
3148
3296
|
try {
|
|
3149
3297
|
const packed = await fs2.readFile(packedPath, "utf8");
|
|
3150
3298
|
for (const line of packed.split("\n")) {
|
|
@@ -3172,18 +3320,18 @@ async function readGitRemoteUrl(gitDir) {
|
|
|
3172
3320
|
}
|
|
3173
3321
|
}
|
|
3174
3322
|
async function resolveGitConfigPath(gitDir) {
|
|
3175
|
-
const directConfig =
|
|
3323
|
+
const directConfig = path10.join(gitDir, "config");
|
|
3176
3324
|
try {
|
|
3177
3325
|
const stat3 = await fs2.stat(directConfig);
|
|
3178
3326
|
if (stat3.isFile()) return directConfig;
|
|
3179
3327
|
} catch {
|
|
3180
3328
|
}
|
|
3181
|
-
const commonDirFile =
|
|
3329
|
+
const commonDirFile = path10.join(gitDir, "commondir");
|
|
3182
3330
|
try {
|
|
3183
3331
|
const commonDir = (await fs2.readFile(commonDirFile, "utf8")).trim();
|
|
3184
3332
|
if (!commonDir) return void 0;
|
|
3185
|
-
const resolvedCommonDir =
|
|
3186
|
-
const commonConfig =
|
|
3333
|
+
const resolvedCommonDir = path10.resolve(gitDir, commonDir);
|
|
3334
|
+
const commonConfig = path10.join(resolvedCommonDir, "config");
|
|
3187
3335
|
const stat3 = await fs2.stat(commonConfig);
|
|
3188
3336
|
if (stat3.isFile()) return commonConfig;
|
|
3189
3337
|
} catch {
|
|
@@ -3574,11 +3722,11 @@ var ScanProgress = class {
|
|
|
3574
3722
|
|
|
3575
3723
|
// src/ui/scan-history.ts
|
|
3576
3724
|
import * as fs3 from "fs/promises";
|
|
3577
|
-
import * as
|
|
3725
|
+
import * as path11 from "path";
|
|
3578
3726
|
var HISTORY_FILENAME = "scan_history.json";
|
|
3579
3727
|
var MAX_RECORDS = 10;
|
|
3580
3728
|
async function loadScanHistory(rootDir) {
|
|
3581
|
-
const filePath =
|
|
3729
|
+
const filePath = path11.join(rootDir, ".vibgrate", HISTORY_FILENAME);
|
|
3582
3730
|
try {
|
|
3583
3731
|
const txt = await fs3.readFile(filePath, "utf8");
|
|
3584
3732
|
const data = JSON.parse(txt);
|
|
@@ -3591,8 +3739,8 @@ async function loadScanHistory(rootDir) {
|
|
|
3591
3739
|
}
|
|
3592
3740
|
}
|
|
3593
3741
|
async function saveScanHistory(rootDir, record) {
|
|
3594
|
-
const dir =
|
|
3595
|
-
const filePath =
|
|
3742
|
+
const dir = path11.join(rootDir, ".vibgrate");
|
|
3743
|
+
const filePath = path11.join(dir, HISTORY_FILENAME);
|
|
3596
3744
|
let history;
|
|
3597
3745
|
const existing = await loadScanHistory(rootDir);
|
|
3598
3746
|
if (existing) {
|
|
@@ -3656,7 +3804,7 @@ function estimateStepDurations(history, currentFileCount) {
|
|
|
3656
3804
|
}
|
|
3657
3805
|
|
|
3658
3806
|
// src/scanners/platform-matrix.ts
|
|
3659
|
-
import * as
|
|
3807
|
+
import * as path12 from "path";
|
|
3660
3808
|
var NATIVE_MODULE_PACKAGES = /* @__PURE__ */ new Set([
|
|
3661
3809
|
// Image / media processing
|
|
3662
3810
|
"sharp",
|
|
@@ -3936,7 +4084,7 @@ async function scanPlatformMatrix(rootDir, cache) {
|
|
|
3936
4084
|
}
|
|
3937
4085
|
result.dockerBaseImages = [...baseImages].sort();
|
|
3938
4086
|
for (const file of [".nvmrc", ".node-version", ".tool-versions"]) {
|
|
3939
|
-
const exists = cache ? await cache.pathExists(
|
|
4087
|
+
const exists = cache ? await cache.pathExists(path12.join(rootDir, file)) : await pathExists(path12.join(rootDir, file));
|
|
3940
4088
|
if (exists) {
|
|
3941
4089
|
result.nodeVersionFiles.push(file);
|
|
3942
4090
|
}
|
|
@@ -4013,7 +4161,7 @@ function scanDependencyRisk(projects) {
|
|
|
4013
4161
|
}
|
|
4014
4162
|
|
|
4015
4163
|
// src/scanners/dependency-graph.ts
|
|
4016
|
-
import * as
|
|
4164
|
+
import * as path13 from "path";
|
|
4017
4165
|
function parsePnpmLock(content) {
|
|
4018
4166
|
const entries = [];
|
|
4019
4167
|
const regex = /^\s+\/?(@?[^@\s][^@\s]*?)@(\d+\.\d+\.\d+[^:\s]*)\s*:/gm;
|
|
@@ -4072,9 +4220,9 @@ async function scanDependencyGraph(rootDir, cache) {
|
|
|
4072
4220
|
phantomDependencies: []
|
|
4073
4221
|
};
|
|
4074
4222
|
let entries = [];
|
|
4075
|
-
const pnpmLock =
|
|
4076
|
-
const npmLock =
|
|
4077
|
-
const yarnLock =
|
|
4223
|
+
const pnpmLock = path13.join(rootDir, "pnpm-lock.yaml");
|
|
4224
|
+
const npmLock = path13.join(rootDir, "package-lock.json");
|
|
4225
|
+
const yarnLock = path13.join(rootDir, "yarn.lock");
|
|
4078
4226
|
const _pathExists = cache ? (p) => cache.pathExists(p) : pathExists;
|
|
4079
4227
|
const _readTextFile = cache ? (p) => cache.readTextFile(p) : readTextFile;
|
|
4080
4228
|
if (await _pathExists(pnpmLock)) {
|
|
@@ -4121,7 +4269,7 @@ async function scanDependencyGraph(rootDir, cache) {
|
|
|
4121
4269
|
for (const pjPath of pkgFiles) {
|
|
4122
4270
|
try {
|
|
4123
4271
|
const pj = cache ? await cache.readJsonFile(pjPath) : await readJsonFile(pjPath);
|
|
4124
|
-
const relPath =
|
|
4272
|
+
const relPath = path13.relative(rootDir, pjPath);
|
|
4125
4273
|
for (const section of ["dependencies", "devDependencies"]) {
|
|
4126
4274
|
const deps = pj[section];
|
|
4127
4275
|
if (!deps) continue;
|
|
@@ -4467,7 +4615,7 @@ function scanToolingInventory(projects) {
|
|
|
4467
4615
|
}
|
|
4468
4616
|
|
|
4469
4617
|
// src/scanners/build-deploy.ts
|
|
4470
|
-
import * as
|
|
4618
|
+
import * as path14 from "path";
|
|
4471
4619
|
var CI_FILES = {
|
|
4472
4620
|
".github/workflows": "github-actions",
|
|
4473
4621
|
".gitlab-ci.yml": "gitlab-ci",
|
|
@@ -4520,17 +4668,17 @@ async function scanBuildDeploy(rootDir, cache) {
|
|
|
4520
4668
|
const _readTextFile = cache ? (p) => cache.readTextFile(p) : readTextFile;
|
|
4521
4669
|
const ciSystems = /* @__PURE__ */ new Set();
|
|
4522
4670
|
for (const [file, system] of Object.entries(CI_FILES)) {
|
|
4523
|
-
const fullPath =
|
|
4671
|
+
const fullPath = path14.join(rootDir, file);
|
|
4524
4672
|
if (await _pathExists(fullPath)) {
|
|
4525
4673
|
ciSystems.add(system);
|
|
4526
4674
|
}
|
|
4527
4675
|
}
|
|
4528
|
-
const ghWorkflowDir =
|
|
4676
|
+
const ghWorkflowDir = path14.join(rootDir, ".github", "workflows");
|
|
4529
4677
|
if (await _pathExists(ghWorkflowDir)) {
|
|
4530
4678
|
try {
|
|
4531
4679
|
if (cache) {
|
|
4532
4680
|
const entries = await cache.walkDir(rootDir);
|
|
4533
|
-
const ghPrefix =
|
|
4681
|
+
const ghPrefix = path14.relative(rootDir, ghWorkflowDir) + path14.sep;
|
|
4534
4682
|
result.ciWorkflowCount = entries.filter(
|
|
4535
4683
|
(e) => e.isFile && e.relPath.startsWith(ghPrefix) && (e.name.endsWith(".yml") || e.name.endsWith(".yaml"))
|
|
4536
4684
|
).length;
|
|
@@ -4581,11 +4729,11 @@ async function scanBuildDeploy(rootDir, cache) {
|
|
|
4581
4729
|
(name) => name.endsWith(".cfn.json") || name.endsWith(".cfn.yaml")
|
|
4582
4730
|
);
|
|
4583
4731
|
if (cfnFiles.length > 0) iacSystems.add("cloudformation");
|
|
4584
|
-
if (await _pathExists(
|
|
4732
|
+
if (await _pathExists(path14.join(rootDir, "Pulumi.yaml"))) iacSystems.add("pulumi");
|
|
4585
4733
|
result.iac = [...iacSystems].sort();
|
|
4586
4734
|
const releaseTools = /* @__PURE__ */ new Set();
|
|
4587
4735
|
for (const [file, tool] of Object.entries(RELEASE_FILES)) {
|
|
4588
|
-
if (await _pathExists(
|
|
4736
|
+
if (await _pathExists(path14.join(rootDir, file))) releaseTools.add(tool);
|
|
4589
4737
|
}
|
|
4590
4738
|
const pkgFiles = cache ? await cache.findPackageJsonFiles(rootDir) : await findPackageJsonFiles(rootDir);
|
|
4591
4739
|
for (const pjPath of pkgFiles) {
|
|
@@ -4610,19 +4758,19 @@ async function scanBuildDeploy(rootDir, cache) {
|
|
|
4610
4758
|
};
|
|
4611
4759
|
const managers = /* @__PURE__ */ new Set();
|
|
4612
4760
|
for (const [file, manager] of Object.entries(lockfileMap)) {
|
|
4613
|
-
if (await _pathExists(
|
|
4761
|
+
if (await _pathExists(path14.join(rootDir, file))) managers.add(manager);
|
|
4614
4762
|
}
|
|
4615
4763
|
result.packageManagers = [...managers].sort();
|
|
4616
4764
|
const monoTools = /* @__PURE__ */ new Set();
|
|
4617
4765
|
for (const [file, tool] of Object.entries(MONOREPO_FILES)) {
|
|
4618
|
-
if (await _pathExists(
|
|
4766
|
+
if (await _pathExists(path14.join(rootDir, file))) monoTools.add(tool);
|
|
4619
4767
|
}
|
|
4620
4768
|
result.monorepoTools = [...monoTools].sort();
|
|
4621
4769
|
return result;
|
|
4622
4770
|
}
|
|
4623
4771
|
|
|
4624
4772
|
// src/scanners/ts-modernity.ts
|
|
4625
|
-
import * as
|
|
4773
|
+
import * as path15 from "path";
|
|
4626
4774
|
async function scanTsModernity(rootDir, cache) {
|
|
4627
4775
|
const result = {
|
|
4628
4776
|
typescriptVersion: null,
|
|
@@ -4660,7 +4808,7 @@ async function scanTsModernity(rootDir, cache) {
|
|
|
4660
4808
|
if (hasEsm && hasCjs) result.moduleType = "mixed";
|
|
4661
4809
|
else if (hasEsm) result.moduleType = "esm";
|
|
4662
4810
|
else if (hasCjs) result.moduleType = "cjs";
|
|
4663
|
-
let tsConfigPath =
|
|
4811
|
+
let tsConfigPath = path15.join(rootDir, "tsconfig.json");
|
|
4664
4812
|
const tsConfigExists = cache ? await cache.pathExists(tsConfigPath) : await pathExists(tsConfigPath);
|
|
4665
4813
|
if (!tsConfigExists) {
|
|
4666
4814
|
const tsConfigs = cache ? await cache.findFiles(rootDir, (name) => name === "tsconfig.json") : await findFiles(rootDir, (name) => name === "tsconfig.json");
|
|
@@ -5007,7 +5155,7 @@ function scanBreakingChangeExposure(projects) {
|
|
|
5007
5155
|
|
|
5008
5156
|
// src/scanners/file-hotspots.ts
|
|
5009
5157
|
import * as fs4 from "fs/promises";
|
|
5010
|
-
import * as
|
|
5158
|
+
import * as path16 from "path";
|
|
5011
5159
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
5012
5160
|
"node_modules",
|
|
5013
5161
|
".git",
|
|
@@ -5052,9 +5200,9 @@ async function scanFileHotspots(rootDir, cache) {
|
|
|
5052
5200
|
const entries = await cache.walkDir(rootDir);
|
|
5053
5201
|
for (const entry of entries) {
|
|
5054
5202
|
if (!entry.isFile) continue;
|
|
5055
|
-
const ext =
|
|
5203
|
+
const ext = path16.extname(entry.name).toLowerCase();
|
|
5056
5204
|
if (SKIP_EXTENSIONS.has(ext)) continue;
|
|
5057
|
-
const depth = entry.relPath.split(
|
|
5205
|
+
const depth = entry.relPath.split(path16.sep).length - 1;
|
|
5058
5206
|
if (depth > maxDepth) maxDepth = depth;
|
|
5059
5207
|
extensionCounts[ext] = (extensionCounts[ext] ?? 0) + 1;
|
|
5060
5208
|
try {
|
|
@@ -5083,15 +5231,15 @@ async function scanFileHotspots(rootDir, cache) {
|
|
|
5083
5231
|
for (const e of entries) {
|
|
5084
5232
|
if (e.isDirectory) {
|
|
5085
5233
|
if (SKIP_DIRS.has(e.name)) continue;
|
|
5086
|
-
await walk(
|
|
5234
|
+
await walk(path16.join(dir, e.name), depth + 1);
|
|
5087
5235
|
} else if (e.isFile) {
|
|
5088
|
-
const ext =
|
|
5236
|
+
const ext = path16.extname(e.name).toLowerCase();
|
|
5089
5237
|
if (SKIP_EXTENSIONS.has(ext)) continue;
|
|
5090
5238
|
extensionCounts[ext] = (extensionCounts[ext] ?? 0) + 1;
|
|
5091
5239
|
try {
|
|
5092
|
-
const stat3 = await fs4.stat(
|
|
5240
|
+
const stat3 = await fs4.stat(path16.join(dir, e.name));
|
|
5093
5241
|
allFiles.push({
|
|
5094
|
-
path:
|
|
5242
|
+
path: path16.relative(rootDir, path16.join(dir, e.name)),
|
|
5095
5243
|
bytes: stat3.size
|
|
5096
5244
|
});
|
|
5097
5245
|
} catch {
|
|
@@ -5114,7 +5262,7 @@ async function scanFileHotspots(rootDir, cache) {
|
|
|
5114
5262
|
}
|
|
5115
5263
|
|
|
5116
5264
|
// src/scanners/security-posture.ts
|
|
5117
|
-
import * as
|
|
5265
|
+
import * as path17 from "path";
|
|
5118
5266
|
var LOCKFILES = {
|
|
5119
5267
|
"pnpm-lock.yaml": "pnpm",
|
|
5120
5268
|
"package-lock.json": "npm",
|
|
@@ -5135,14 +5283,14 @@ async function scanSecurityPosture(rootDir, cache) {
|
|
|
5135
5283
|
const _readTextFile = cache ? (p) => cache.readTextFile(p) : readTextFile;
|
|
5136
5284
|
const foundLockfiles = [];
|
|
5137
5285
|
for (const [file, type] of Object.entries(LOCKFILES)) {
|
|
5138
|
-
if (await _pathExists(
|
|
5286
|
+
if (await _pathExists(path17.join(rootDir, file))) {
|
|
5139
5287
|
foundLockfiles.push(type);
|
|
5140
5288
|
}
|
|
5141
5289
|
}
|
|
5142
5290
|
result.lockfilePresent = foundLockfiles.length > 0;
|
|
5143
5291
|
result.multipleLockfileTypes = foundLockfiles.length > 1;
|
|
5144
5292
|
result.lockfileTypes = foundLockfiles.sort();
|
|
5145
|
-
const gitignorePath =
|
|
5293
|
+
const gitignorePath = path17.join(rootDir, ".gitignore");
|
|
5146
5294
|
if (await _pathExists(gitignorePath)) {
|
|
5147
5295
|
try {
|
|
5148
5296
|
const content = await _readTextFile(gitignorePath);
|
|
@@ -5157,7 +5305,7 @@ async function scanSecurityPosture(rootDir, cache) {
|
|
|
5157
5305
|
}
|
|
5158
5306
|
}
|
|
5159
5307
|
for (const envFile of [".env", ".env.local", ".env.development", ".env.production"]) {
|
|
5160
|
-
if (await _pathExists(
|
|
5308
|
+
if (await _pathExists(path17.join(rootDir, envFile))) {
|
|
5161
5309
|
if (!result.gitignoreCoversEnv) {
|
|
5162
5310
|
result.envFilesTracked = true;
|
|
5163
5311
|
break;
|
|
@@ -5169,7 +5317,7 @@ async function scanSecurityPosture(rootDir, cache) {
|
|
|
5169
5317
|
|
|
5170
5318
|
// src/scanners/security-scanners.ts
|
|
5171
5319
|
import { spawn as spawn3 } from "child_process";
|
|
5172
|
-
import * as
|
|
5320
|
+
import * as path18 from "path";
|
|
5173
5321
|
var TOOL_MATRIX = [
|
|
5174
5322
|
{ key: "semgrep", category: "sast", command: "semgrep", versionArgs: ["--version"], minRecommendedVersion: "1.75.0" },
|
|
5175
5323
|
{ key: "gitleaks", category: "secrets", command: "gitleaks", versionArgs: ["version"], minRecommendedVersion: "8.20.0" },
|
|
@@ -5264,7 +5412,7 @@ async function detectSecretHeuristics(rootDir, cache) {
|
|
|
5264
5412
|
const findings = [];
|
|
5265
5413
|
for (const entry of entries) {
|
|
5266
5414
|
if (!entry.isFile) continue;
|
|
5267
|
-
const ext =
|
|
5415
|
+
const ext = path18.extname(entry.name).toLowerCase();
|
|
5268
5416
|
if (ext && [".png", ".jpg", ".jpeg", ".gif", ".zip", ".pdf"].includes(ext)) continue;
|
|
5269
5417
|
const content = await cache.readTextFile(entry.absPath);
|
|
5270
5418
|
if (!content || content.length > 3e5) continue;
|
|
@@ -5287,9 +5435,9 @@ async function scanSecurityScanners(rootDir, cache, runner = defaultRunner) {
|
|
|
5287
5435
|
const [semgrep, gitleaks, trufflehog] = await Promise.all(TOOL_MATRIX.map((tool) => assessTool(tool, runner)));
|
|
5288
5436
|
const heuristicFindings = await detectSecretHeuristics(rootDir, cache);
|
|
5289
5437
|
const configFiles = {
|
|
5290
|
-
semgrep: await cache.pathExists(
|
|
5291
|
-
gitleaks: await cache.pathExists(
|
|
5292
|
-
trufflehog: await cache.pathExists(
|
|
5438
|
+
semgrep: await cache.pathExists(path18.join(rootDir, ".semgrep.yml")) || await cache.pathExists(path18.join(rootDir, ".semgrep.yaml")),
|
|
5439
|
+
gitleaks: await cache.pathExists(path18.join(rootDir, ".gitleaks.toml")),
|
|
5440
|
+
trufflehog: await cache.pathExists(path18.join(rootDir, ".trufflehog.yml")) || await cache.pathExists(path18.join(rootDir, ".trufflehog.yaml"))
|
|
5293
5441
|
};
|
|
5294
5442
|
return {
|
|
5295
5443
|
semgrep,
|
|
@@ -5714,7 +5862,7 @@ function scanServiceDependencies(projects) {
|
|
|
5714
5862
|
}
|
|
5715
5863
|
|
|
5716
5864
|
// src/scanners/architecture.ts
|
|
5717
|
-
import * as
|
|
5865
|
+
import * as path19 from "path";
|
|
5718
5866
|
import * as fs5 from "fs/promises";
|
|
5719
5867
|
var ARCHETYPE_SIGNALS = [
|
|
5720
5868
|
// Meta-frameworks (highest priority — they imply routing patterns)
|
|
@@ -6013,9 +6161,9 @@ async function walkSourceFiles(rootDir, cache) {
|
|
|
6013
6161
|
const entries = await cache.walkDir(rootDir);
|
|
6014
6162
|
return entries.filter((e) => {
|
|
6015
6163
|
if (!e.isFile) return false;
|
|
6016
|
-
const name =
|
|
6164
|
+
const name = path19.basename(e.absPath);
|
|
6017
6165
|
if (name.startsWith(".") && name !== ".") return false;
|
|
6018
|
-
const ext =
|
|
6166
|
+
const ext = path19.extname(name);
|
|
6019
6167
|
return SOURCE_EXTENSIONS.has(ext);
|
|
6020
6168
|
}).map((e) => e.relPath);
|
|
6021
6169
|
}
|
|
@@ -6029,15 +6177,15 @@ async function walkSourceFiles(rootDir, cache) {
|
|
|
6029
6177
|
}
|
|
6030
6178
|
for (const entry of entries) {
|
|
6031
6179
|
if (entry.name.startsWith(".") && entry.name !== ".") continue;
|
|
6032
|
-
const fullPath =
|
|
6180
|
+
const fullPath = path19.join(dir, entry.name);
|
|
6033
6181
|
if (entry.isDirectory()) {
|
|
6034
6182
|
if (!IGNORE_DIRS.has(entry.name)) {
|
|
6035
6183
|
await walk(fullPath);
|
|
6036
6184
|
}
|
|
6037
6185
|
} else if (entry.isFile()) {
|
|
6038
|
-
const ext =
|
|
6186
|
+
const ext = path19.extname(entry.name);
|
|
6039
6187
|
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
6040
|
-
files.push(
|
|
6188
|
+
files.push(path19.relative(rootDir, fullPath));
|
|
6041
6189
|
}
|
|
6042
6190
|
}
|
|
6043
6191
|
}
|
|
@@ -6061,7 +6209,7 @@ function classifyFile(filePath, archetype) {
|
|
|
6061
6209
|
}
|
|
6062
6210
|
}
|
|
6063
6211
|
if (!bestMatch || bestMatch.confidence < 0.7) {
|
|
6064
|
-
const baseName =
|
|
6212
|
+
const baseName = path19.basename(filePath, path19.extname(filePath));
|
|
6065
6213
|
const cleanBase = baseName.replace(/\.(test|spec)$/, "");
|
|
6066
6214
|
for (const rule of SUFFIX_RULES) {
|
|
6067
6215
|
if (cleanBase.endsWith(rule.suffix)) {
|
|
@@ -6170,7 +6318,7 @@ function generateLayerFlowMermaid(layers) {
|
|
|
6170
6318
|
return lines.join("\n");
|
|
6171
6319
|
}
|
|
6172
6320
|
async function buildProjectArchitectureMermaid(rootDir, project, archetype, cache) {
|
|
6173
|
-
const projectRoot =
|
|
6321
|
+
const projectRoot = path19.resolve(rootDir, project.path || ".");
|
|
6174
6322
|
const allFiles = await walkSourceFiles(projectRoot, cache);
|
|
6175
6323
|
const layerSet = /* @__PURE__ */ new Set();
|
|
6176
6324
|
for (const rel of allFiles) {
|
|
@@ -6296,7 +6444,7 @@ async function scanArchitecture(rootDir, projects, tooling, services, cache) {
|
|
|
6296
6444
|
}
|
|
6297
6445
|
|
|
6298
6446
|
// src/scanners/code-quality.ts
|
|
6299
|
-
import * as
|
|
6447
|
+
import * as path20 from "path";
|
|
6300
6448
|
import * as ts from "typescript";
|
|
6301
6449
|
var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
6302
6450
|
var DEFAULT_RESULT = {
|
|
@@ -6327,9 +6475,9 @@ async function scanCodeQuality(rootDir, cache) {
|
|
|
6327
6475
|
continue;
|
|
6328
6476
|
}
|
|
6329
6477
|
if (!raw.trim()) continue;
|
|
6330
|
-
const rel = normalizeModuleId(
|
|
6478
|
+
const rel = normalizeModuleId(path20.relative(rootDir, filePath));
|
|
6331
6479
|
const source = ts.createSourceFile(filePath, raw, ts.ScriptTarget.Latest, true);
|
|
6332
|
-
const imports = collectLocalImports(source,
|
|
6480
|
+
const imports = collectLocalImports(source, path20.dirname(filePath), rootDir);
|
|
6333
6481
|
depGraph.set(rel, imports);
|
|
6334
6482
|
const fileMetrics = computeFileMetrics(source, raw);
|
|
6335
6483
|
totalFunctions += fileMetrics.functionsAnalyzed;
|
|
@@ -6363,9 +6511,9 @@ async function scanCodeQuality(rootDir, cache) {
|
|
|
6363
6511
|
async function findSourceFiles(rootDir, cache) {
|
|
6364
6512
|
if (cache) {
|
|
6365
6513
|
const entries = await cache.walkDir(rootDir);
|
|
6366
|
-
return entries.filter((entry) => entry.isFile && SOURCE_EXTENSIONS2.has(
|
|
6514
|
+
return entries.filter((entry) => entry.isFile && SOURCE_EXTENSIONS2.has(path20.extname(entry.name).toLowerCase())).map((entry) => entry.absPath);
|
|
6367
6515
|
}
|
|
6368
|
-
const files = await findFiles(rootDir, (name) => SOURCE_EXTENSIONS2.has(
|
|
6516
|
+
const files = await findFiles(rootDir, (name) => SOURCE_EXTENSIONS2.has(path20.extname(name).toLowerCase()));
|
|
6369
6517
|
return files;
|
|
6370
6518
|
}
|
|
6371
6519
|
function collectLocalImports(source, fileDir, rootDir) {
|
|
@@ -6386,8 +6534,8 @@ function collectLocalImports(source, fileDir, rootDir) {
|
|
|
6386
6534
|
}
|
|
6387
6535
|
function resolveLocalImport(specifier, fileDir, rootDir) {
|
|
6388
6536
|
if (!specifier.startsWith(".")) return null;
|
|
6389
|
-
const rawTarget =
|
|
6390
|
-
const normalized =
|
|
6537
|
+
const rawTarget = path20.resolve(fileDir, specifier);
|
|
6538
|
+
const normalized = path20.relative(rootDir, rawTarget).replace(/\\/g, "/");
|
|
6391
6539
|
if (!normalized || normalized.startsWith("..")) return null;
|
|
6392
6540
|
return normalizeModuleId(normalized);
|
|
6393
6541
|
}
|
|
@@ -6529,7 +6677,7 @@ function visitEach(node, cb) {
|
|
|
6529
6677
|
|
|
6530
6678
|
// src/scanners/owasp-category-mapping.ts
|
|
6531
6679
|
import { spawn as spawn4 } from "child_process";
|
|
6532
|
-
import * as
|
|
6680
|
+
import * as path21 from "path";
|
|
6533
6681
|
var OWASP_CONFIG = "p/owasp-top-ten";
|
|
6534
6682
|
var DEFAULT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
6535
6683
|
".js",
|
|
@@ -6608,7 +6756,7 @@ function parseFindings(results, rootDir) {
|
|
|
6608
6756
|
const metadata = r.extra?.metadata;
|
|
6609
6757
|
return {
|
|
6610
6758
|
ruleId: r.check_id ?? "unknown",
|
|
6611
|
-
path: r.path ?
|
|
6759
|
+
path: r.path ? path21.relative(rootDir, path21.resolve(rootDir, r.path)) : "",
|
|
6612
6760
|
line: r.start?.line ?? 1,
|
|
6613
6761
|
endLine: r.end?.line,
|
|
6614
6762
|
message: r.extra?.message ?? "Potential security issue",
|
|
@@ -6668,7 +6816,7 @@ async function scanOwaspCategoryMapping(rootDir, cache, options = {}, runner = r
|
|
|
6668
6816
|
}
|
|
6669
6817
|
}
|
|
6670
6818
|
const entries = cache ? await cache.walkDir(rootDir) : [];
|
|
6671
|
-
const files = entries.filter((e) => e.isFile && DEFAULT_EXTENSIONS.has(
|
|
6819
|
+
const files = entries.filter((e) => e.isFile && DEFAULT_EXTENSIONS.has(path21.extname(e.name).toLowerCase()));
|
|
6672
6820
|
const findings = [];
|
|
6673
6821
|
const errors = [];
|
|
6674
6822
|
let scannedFiles = 0;
|
|
@@ -7036,20 +7184,27 @@ function buildDefs() {
|
|
|
7036
7184
|
function generateWorkspaceRelationshipMermaid(projects) {
|
|
7037
7185
|
const lines = ["flowchart LR"];
|
|
7038
7186
|
const byPath = new Map(projects.map((p) => [p.path, p]));
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
lines.push(`${id}["${escapeLabel(nodeLabel(project))}"]`);
|
|
7042
|
-
lines.push(`class ${id} ${scoreClass(project.drift?.score)}`);
|
|
7043
|
-
}
|
|
7187
|
+
const edges = [];
|
|
7188
|
+
const connectedIds = /* @__PURE__ */ new Set();
|
|
7044
7189
|
for (const project of projects) {
|
|
7045
7190
|
const fromId = sanitizeId(project.projectId || project.path || project.name);
|
|
7046
7191
|
for (const ref of project.projectReferences ?? []) {
|
|
7047
7192
|
const target = byPath.get(ref.path);
|
|
7048
7193
|
if (!target) continue;
|
|
7049
7194
|
const toId = sanitizeId(target.projectId || target.path || target.name);
|
|
7050
|
-
|
|
7195
|
+
edges.push(`${fromId} --> ${toId}`);
|
|
7196
|
+
connectedIds.add(fromId);
|
|
7197
|
+
connectedIds.add(toId);
|
|
7051
7198
|
}
|
|
7052
7199
|
}
|
|
7200
|
+
const showAll = connectedIds.size === 0;
|
|
7201
|
+
for (const project of projects) {
|
|
7202
|
+
const id = sanitizeId(project.projectId || project.path || project.name);
|
|
7203
|
+
if (!showAll && !connectedIds.has(id)) continue;
|
|
7204
|
+
lines.push(`${id}["${escapeLabel(nodeLabel(project))}"]`);
|
|
7205
|
+
lines.push(`class ${id} ${scoreClass(project.drift?.score)}`);
|
|
7206
|
+
}
|
|
7207
|
+
lines.push(...edges);
|
|
7053
7208
|
lines.push(...buildDefs());
|
|
7054
7209
|
return { mermaid: lines.join("\n") };
|
|
7055
7210
|
}
|
|
@@ -7109,17 +7264,17 @@ async function discoverSolutions(rootDir, fileCache) {
|
|
|
7109
7264
|
for (const solutionFile of solutionFiles) {
|
|
7110
7265
|
try {
|
|
7111
7266
|
const content = await fileCache.readTextFile(solutionFile);
|
|
7112
|
-
const dir =
|
|
7113
|
-
const relSolutionPath =
|
|
7267
|
+
const dir = path22.dirname(solutionFile);
|
|
7268
|
+
const relSolutionPath = path22.relative(rootDir, solutionFile).replace(/\\/g, "/");
|
|
7114
7269
|
const projectPaths = /* @__PURE__ */ new Set();
|
|
7115
7270
|
const projectRegex = /Project\("[^"]*"\)\s*=\s*"([^"]*)",\s*"([^"]+\.csproj)"/g;
|
|
7116
7271
|
let match;
|
|
7117
7272
|
while ((match = projectRegex.exec(content)) !== null) {
|
|
7118
7273
|
const projectRelative = match[2];
|
|
7119
|
-
const absProjectPath =
|
|
7120
|
-
projectPaths.add(
|
|
7274
|
+
const absProjectPath = path22.resolve(dir, projectRelative.replace(/\\/g, "/"));
|
|
7275
|
+
projectPaths.add(path22.relative(rootDir, absProjectPath).replace(/\\/g, "/"));
|
|
7121
7276
|
}
|
|
7122
|
-
const solutionName =
|
|
7277
|
+
const solutionName = path22.basename(solutionFile, path22.extname(solutionFile));
|
|
7123
7278
|
parsed.push({
|
|
7124
7279
|
path: relSolutionPath,
|
|
7125
7280
|
name: solutionName,
|
|
@@ -7175,6 +7330,7 @@ async function runScan(rootDir, opts) {
|
|
|
7175
7330
|
{ id: "dotnet", label: "Scanning .NET projects", weight: 2 },
|
|
7176
7331
|
{ id: "python", label: "Scanning Python projects", weight: 3 },
|
|
7177
7332
|
{ id: "java", label: "Scanning Java projects", weight: 3 },
|
|
7333
|
+
{ id: "polyglot", label: "Scanning additional language projects", weight: 2 },
|
|
7178
7334
|
...scanners !== false ? [
|
|
7179
7335
|
...scannerPolicy.platformMatrix && scanners?.platformMatrix?.enabled !== false ? [{ id: "platform", label: "Platform matrix" }] : [],
|
|
7180
7336
|
...scannerPolicy.toolingInventory && scanners?.toolingInventory?.enabled !== false ? [{ id: "tooling", label: "Tooling inventory" }] : [],
|
|
@@ -7271,7 +7427,16 @@ async function runScan(rootDir, opts) {
|
|
|
7271
7427
|
filesScanned += javaProjects.length;
|
|
7272
7428
|
progress.addProjects(javaProjects.length);
|
|
7273
7429
|
progress.completeStep("java", `${javaProjects.length} project${javaProjects.length !== 1 ? "s" : ""}`, javaProjects.length);
|
|
7274
|
-
|
|
7430
|
+
progress.startStep("polyglot");
|
|
7431
|
+
const polyglotProjects = await scanPolyglotProjects(rootDir, fileCache);
|
|
7432
|
+
for (const p of polyglotProjects) {
|
|
7433
|
+
progress.addDependencies(p.dependencies.length);
|
|
7434
|
+
progress.addFrameworks(p.frameworks.length);
|
|
7435
|
+
}
|
|
7436
|
+
filesScanned += polyglotProjects.length;
|
|
7437
|
+
progress.addProjects(polyglotProjects.length);
|
|
7438
|
+
progress.completeStep("polyglot", `${polyglotProjects.length} project${polyglotProjects.length !== 1 ? "s" : ""}`, polyglotProjects.length);
|
|
7439
|
+
const allProjects = [...nodeProjects, ...dotnetProjects, ...pythonProjects, ...javaProjects, ...polyglotProjects];
|
|
7275
7440
|
const dsn = opts.dsn || process.env.VIBGRATE_DSN;
|
|
7276
7441
|
const parsedDsn = dsn ? parseDsn(dsn) : null;
|
|
7277
7442
|
const workspaceId = parsedDsn?.workspaceId;
|
|
@@ -7279,7 +7444,7 @@ async function runScan(rootDir, opts) {
|
|
|
7279
7444
|
project.drift = computeDriftScore([project]);
|
|
7280
7445
|
project.projectId = computeProjectId(project.path, project.name, workspaceId);
|
|
7281
7446
|
}
|
|
7282
|
-
const solutionsManifestPath =
|
|
7447
|
+
const solutionsManifestPath = path22.join(rootDir, ".vibgrate", "solutions.json");
|
|
7283
7448
|
const persistedSolutionIds = /* @__PURE__ */ new Map();
|
|
7284
7449
|
if (await pathExists(solutionsManifestPath)) {
|
|
7285
7450
|
try {
|
|
@@ -7589,7 +7754,7 @@ async function runScan(rootDir, opts) {
|
|
|
7589
7754
|
schemaVersion: "1.0",
|
|
7590
7755
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7591
7756
|
vibgrateVersion: VERSION,
|
|
7592
|
-
rootPath:
|
|
7757
|
+
rootPath: path22.basename(rootDir),
|
|
7593
7758
|
...vcs.type !== "unknown" ? { vcs } : {},
|
|
7594
7759
|
repository,
|
|
7595
7760
|
projects: allProjects,
|
|
@@ -7603,7 +7768,7 @@ async function runScan(rootDir, opts) {
|
|
|
7603
7768
|
relationshipDiagram
|
|
7604
7769
|
};
|
|
7605
7770
|
if (opts.baseline) {
|
|
7606
|
-
const baselinePath =
|
|
7771
|
+
const baselinePath = path22.resolve(opts.baseline);
|
|
7607
7772
|
if (await pathExists(baselinePath)) {
|
|
7608
7773
|
try {
|
|
7609
7774
|
const baseline = await readJsonFile(baselinePath);
|
|
@@ -7615,10 +7780,10 @@ async function runScan(rootDir, opts) {
|
|
|
7615
7780
|
}
|
|
7616
7781
|
}
|
|
7617
7782
|
if (!opts.noLocalArtifacts && !maxPrivacyMode) {
|
|
7618
|
-
const vibgrateDir =
|
|
7783
|
+
const vibgrateDir = path22.join(rootDir, ".vibgrate");
|
|
7619
7784
|
await ensureDir(vibgrateDir);
|
|
7620
|
-
await writeJsonFile(
|
|
7621
|
-
await writeJsonFile(
|
|
7785
|
+
await writeJsonFile(path22.join(vibgrateDir, "scan_result.json"), artifact);
|
|
7786
|
+
await writeJsonFile(path22.join(vibgrateDir, "solutions.json"), {
|
|
7622
7787
|
scannedAt: artifact.timestamp,
|
|
7623
7788
|
solutions: solutions.map((solution) => ({
|
|
7624
7789
|
solutionId: solution.solutionId,
|
|
@@ -7639,10 +7804,10 @@ async function runScan(rootDir, opts) {
|
|
|
7639
7804
|
if (!opts.noLocalArtifacts && !maxPrivacyMode) {
|
|
7640
7805
|
for (const project of allProjects) {
|
|
7641
7806
|
if (project.drift && project.path) {
|
|
7642
|
-
const projectDir =
|
|
7643
|
-
const projectVibgrateDir =
|
|
7807
|
+
const projectDir = path22.resolve(rootDir, project.path);
|
|
7808
|
+
const projectVibgrateDir = path22.join(projectDir, ".vibgrate");
|
|
7644
7809
|
await ensureDir(projectVibgrateDir);
|
|
7645
|
-
await writeJsonFile(
|
|
7810
|
+
await writeJsonFile(path22.join(projectVibgrateDir, "project_score.json"), {
|
|
7646
7811
|
projectId: project.projectId,
|
|
7647
7812
|
name: project.name,
|
|
7648
7813
|
type: project.type,
|
|
@@ -7662,7 +7827,7 @@ async function runScan(rootDir, opts) {
|
|
|
7662
7827
|
if (opts.format === "json") {
|
|
7663
7828
|
const jsonStr = JSON.stringify(artifact, null, 2);
|
|
7664
7829
|
if (opts.out) {
|
|
7665
|
-
await writeTextFile(
|
|
7830
|
+
await writeTextFile(path22.resolve(opts.out), jsonStr);
|
|
7666
7831
|
console.log(chalk6.green("\u2714") + ` JSON written to ${opts.out}`);
|
|
7667
7832
|
} else {
|
|
7668
7833
|
console.log(jsonStr);
|
|
@@ -7671,7 +7836,7 @@ async function runScan(rootDir, opts) {
|
|
|
7671
7836
|
const sarif = formatSarif(artifact);
|
|
7672
7837
|
const sarifStr = JSON.stringify(sarif, null, 2);
|
|
7673
7838
|
if (opts.out) {
|
|
7674
|
-
await writeTextFile(
|
|
7839
|
+
await writeTextFile(path22.resolve(opts.out), sarifStr);
|
|
7675
7840
|
console.log(chalk6.green("\u2714") + ` SARIF written to ${opts.out}`);
|
|
7676
7841
|
} else {
|
|
7677
7842
|
console.log(sarifStr);
|
|
@@ -7680,14 +7845,14 @@ async function runScan(rootDir, opts) {
|
|
|
7680
7845
|
const text = formatText(artifact);
|
|
7681
7846
|
console.log(text);
|
|
7682
7847
|
if (opts.out) {
|
|
7683
|
-
await writeTextFile(
|
|
7848
|
+
await writeTextFile(path22.resolve(opts.out), text);
|
|
7684
7849
|
}
|
|
7685
7850
|
}
|
|
7686
7851
|
return artifact;
|
|
7687
7852
|
}
|
|
7688
7853
|
async function buildRepositoryInfo(rootDir, remoteUrl, ciSystems) {
|
|
7689
|
-
const packageJsonPath =
|
|
7690
|
-
let name =
|
|
7854
|
+
const packageJsonPath = path22.join(rootDir, "package.json");
|
|
7855
|
+
let name = path22.basename(rootDir);
|
|
7691
7856
|
let version;
|
|
7692
7857
|
if (await pathExists(packageJsonPath)) {
|
|
7693
7858
|
try {
|
|
@@ -7779,7 +7944,7 @@ function parseNonNegativeNumber(value, label) {
|
|
|
7779
7944
|
return parsed;
|
|
7780
7945
|
}
|
|
7781
7946
|
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").option("--install-tools", "Auto-install missing security scanners via Homebrew").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("--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) => {
|
|
7782
|
-
const rootDir =
|
|
7947
|
+
const rootDir = path22.resolve(targetPath);
|
|
7783
7948
|
if (!await pathExists(rootDir)) {
|
|
7784
7949
|
console.error(chalk6.red(`Path does not exist: ${rootDir}`));
|
|
7785
7950
|
process.exit(1);
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-PTMLMDZU.js";
|
|
5
5
|
import {
|
|
6
6
|
baselineCommand
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4M7MNV6G.js";
|
|
8
8
|
import {
|
|
9
9
|
VERSION,
|
|
10
10
|
dsnCommand,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
pushCommand,
|
|
13
13
|
scanCommand,
|
|
14
14
|
writeDefaultConfig
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-E53FDKZE.js";
|
|
16
16
|
import {
|
|
17
17
|
ensureDir,
|
|
18
18
|
pathExists,
|
|
@@ -41,7 +41,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
41
41
|
console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
|
|
42
42
|
}
|
|
43
43
|
if (opts.baseline) {
|
|
44
|
-
const { runBaseline } = await import("./baseline-
|
|
44
|
+
const { runBaseline } = await import("./baseline-M6HQP4E5.js");
|
|
45
45
|
await runBaseline(rootDir);
|
|
46
46
|
}
|
|
47
47
|
console.log("");
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
type DepSection = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies';
|
|
2
2
|
type RiskLevel = 'low' | 'moderate' | 'high';
|
|
3
|
-
type ProjectType = 'node' | 'dotnet' | 'python' | 'java';
|
|
3
|
+
type ProjectType = 'node' | 'dotnet' | 'python' | 'java' | 'go' | 'rust' | 'php' | 'typescript' | 'ruby' | 'swift' | 'kotlin' | 'dart' | 'scala' | 'r' | 'objective-c' | 'elixir' | 'haskell' | 'lua' | 'perl' | 'julia' | 'shell' | 'clojure' | 'groovy' | 'c' | 'cpp' | 'cobol' | 'fortran' | 'visual-basic' | 'pascal' | 'ada' | 'assembly' | 'rpg';
|
|
4
4
|
type OutputFormat = 'text' | 'json' | 'sarif';
|
|
5
5
|
interface DependencyRow {
|
|
6
6
|
package: string;
|
package/dist/index.js
CHANGED