@vibgrate/cli 1.0.47 → 1.0.48
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,26 @@ 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
|
+
for (const project of projects) {
|
|
7201
|
+
const id = sanitizeId(project.projectId || project.path || project.name);
|
|
7202
|
+
if (!connectedIds.has(id)) continue;
|
|
7203
|
+
lines.push(`${id}["${escapeLabel(nodeLabel(project))}"]`);
|
|
7204
|
+
lines.push(`class ${id} ${scoreClass(project.drift?.score)}`);
|
|
7205
|
+
}
|
|
7206
|
+
lines.push(...edges);
|
|
7053
7207
|
lines.push(...buildDefs());
|
|
7054
7208
|
return { mermaid: lines.join("\n") };
|
|
7055
7209
|
}
|
|
@@ -7109,17 +7263,17 @@ async function discoverSolutions(rootDir, fileCache) {
|
|
|
7109
7263
|
for (const solutionFile of solutionFiles) {
|
|
7110
7264
|
try {
|
|
7111
7265
|
const content = await fileCache.readTextFile(solutionFile);
|
|
7112
|
-
const dir =
|
|
7113
|
-
const relSolutionPath =
|
|
7266
|
+
const dir = path22.dirname(solutionFile);
|
|
7267
|
+
const relSolutionPath = path22.relative(rootDir, solutionFile).replace(/\\/g, "/");
|
|
7114
7268
|
const projectPaths = /* @__PURE__ */ new Set();
|
|
7115
7269
|
const projectRegex = /Project\("[^"]*"\)\s*=\s*"([^"]*)",\s*"([^"]+\.csproj)"/g;
|
|
7116
7270
|
let match;
|
|
7117
7271
|
while ((match = projectRegex.exec(content)) !== null) {
|
|
7118
7272
|
const projectRelative = match[2];
|
|
7119
|
-
const absProjectPath =
|
|
7120
|
-
projectPaths.add(
|
|
7273
|
+
const absProjectPath = path22.resolve(dir, projectRelative.replace(/\\/g, "/"));
|
|
7274
|
+
projectPaths.add(path22.relative(rootDir, absProjectPath).replace(/\\/g, "/"));
|
|
7121
7275
|
}
|
|
7122
|
-
const solutionName =
|
|
7276
|
+
const solutionName = path22.basename(solutionFile, path22.extname(solutionFile));
|
|
7123
7277
|
parsed.push({
|
|
7124
7278
|
path: relSolutionPath,
|
|
7125
7279
|
name: solutionName,
|
|
@@ -7175,6 +7329,7 @@ async function runScan(rootDir, opts) {
|
|
|
7175
7329
|
{ id: "dotnet", label: "Scanning .NET projects", weight: 2 },
|
|
7176
7330
|
{ id: "python", label: "Scanning Python projects", weight: 3 },
|
|
7177
7331
|
{ id: "java", label: "Scanning Java projects", weight: 3 },
|
|
7332
|
+
{ id: "polyglot", label: "Scanning additional language projects", weight: 2 },
|
|
7178
7333
|
...scanners !== false ? [
|
|
7179
7334
|
...scannerPolicy.platformMatrix && scanners?.platformMatrix?.enabled !== false ? [{ id: "platform", label: "Platform matrix" }] : [],
|
|
7180
7335
|
...scannerPolicy.toolingInventory && scanners?.toolingInventory?.enabled !== false ? [{ id: "tooling", label: "Tooling inventory" }] : [],
|
|
@@ -7271,7 +7426,16 @@ async function runScan(rootDir, opts) {
|
|
|
7271
7426
|
filesScanned += javaProjects.length;
|
|
7272
7427
|
progress.addProjects(javaProjects.length);
|
|
7273
7428
|
progress.completeStep("java", `${javaProjects.length} project${javaProjects.length !== 1 ? "s" : ""}`, javaProjects.length);
|
|
7274
|
-
|
|
7429
|
+
progress.startStep("polyglot");
|
|
7430
|
+
const polyglotProjects = await scanPolyglotProjects(rootDir, fileCache);
|
|
7431
|
+
for (const p of polyglotProjects) {
|
|
7432
|
+
progress.addDependencies(p.dependencies.length);
|
|
7433
|
+
progress.addFrameworks(p.frameworks.length);
|
|
7434
|
+
}
|
|
7435
|
+
filesScanned += polyglotProjects.length;
|
|
7436
|
+
progress.addProjects(polyglotProjects.length);
|
|
7437
|
+
progress.completeStep("polyglot", `${polyglotProjects.length} project${polyglotProjects.length !== 1 ? "s" : ""}`, polyglotProjects.length);
|
|
7438
|
+
const allProjects = [...nodeProjects, ...dotnetProjects, ...pythonProjects, ...javaProjects, ...polyglotProjects];
|
|
7275
7439
|
const dsn = opts.dsn || process.env.VIBGRATE_DSN;
|
|
7276
7440
|
const parsedDsn = dsn ? parseDsn(dsn) : null;
|
|
7277
7441
|
const workspaceId = parsedDsn?.workspaceId;
|
|
@@ -7279,7 +7443,7 @@ async function runScan(rootDir, opts) {
|
|
|
7279
7443
|
project.drift = computeDriftScore([project]);
|
|
7280
7444
|
project.projectId = computeProjectId(project.path, project.name, workspaceId);
|
|
7281
7445
|
}
|
|
7282
|
-
const solutionsManifestPath =
|
|
7446
|
+
const solutionsManifestPath = path22.join(rootDir, ".vibgrate", "solutions.json");
|
|
7283
7447
|
const persistedSolutionIds = /* @__PURE__ */ new Map();
|
|
7284
7448
|
if (await pathExists(solutionsManifestPath)) {
|
|
7285
7449
|
try {
|
|
@@ -7589,7 +7753,7 @@ async function runScan(rootDir, opts) {
|
|
|
7589
7753
|
schemaVersion: "1.0",
|
|
7590
7754
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7591
7755
|
vibgrateVersion: VERSION,
|
|
7592
|
-
rootPath:
|
|
7756
|
+
rootPath: path22.basename(rootDir),
|
|
7593
7757
|
...vcs.type !== "unknown" ? { vcs } : {},
|
|
7594
7758
|
repository,
|
|
7595
7759
|
projects: allProjects,
|
|
@@ -7603,7 +7767,7 @@ async function runScan(rootDir, opts) {
|
|
|
7603
7767
|
relationshipDiagram
|
|
7604
7768
|
};
|
|
7605
7769
|
if (opts.baseline) {
|
|
7606
|
-
const baselinePath =
|
|
7770
|
+
const baselinePath = path22.resolve(opts.baseline);
|
|
7607
7771
|
if (await pathExists(baselinePath)) {
|
|
7608
7772
|
try {
|
|
7609
7773
|
const baseline = await readJsonFile(baselinePath);
|
|
@@ -7615,10 +7779,10 @@ async function runScan(rootDir, opts) {
|
|
|
7615
7779
|
}
|
|
7616
7780
|
}
|
|
7617
7781
|
if (!opts.noLocalArtifacts && !maxPrivacyMode) {
|
|
7618
|
-
const vibgrateDir =
|
|
7782
|
+
const vibgrateDir = path22.join(rootDir, ".vibgrate");
|
|
7619
7783
|
await ensureDir(vibgrateDir);
|
|
7620
|
-
await writeJsonFile(
|
|
7621
|
-
await writeJsonFile(
|
|
7784
|
+
await writeJsonFile(path22.join(vibgrateDir, "scan_result.json"), artifact);
|
|
7785
|
+
await writeJsonFile(path22.join(vibgrateDir, "solutions.json"), {
|
|
7622
7786
|
scannedAt: artifact.timestamp,
|
|
7623
7787
|
solutions: solutions.map((solution) => ({
|
|
7624
7788
|
solutionId: solution.solutionId,
|
|
@@ -7639,10 +7803,10 @@ async function runScan(rootDir, opts) {
|
|
|
7639
7803
|
if (!opts.noLocalArtifacts && !maxPrivacyMode) {
|
|
7640
7804
|
for (const project of allProjects) {
|
|
7641
7805
|
if (project.drift && project.path) {
|
|
7642
|
-
const projectDir =
|
|
7643
|
-
const projectVibgrateDir =
|
|
7806
|
+
const projectDir = path22.resolve(rootDir, project.path);
|
|
7807
|
+
const projectVibgrateDir = path22.join(projectDir, ".vibgrate");
|
|
7644
7808
|
await ensureDir(projectVibgrateDir);
|
|
7645
|
-
await writeJsonFile(
|
|
7809
|
+
await writeJsonFile(path22.join(projectVibgrateDir, "project_score.json"), {
|
|
7646
7810
|
projectId: project.projectId,
|
|
7647
7811
|
name: project.name,
|
|
7648
7812
|
type: project.type,
|
|
@@ -7662,7 +7826,7 @@ async function runScan(rootDir, opts) {
|
|
|
7662
7826
|
if (opts.format === "json") {
|
|
7663
7827
|
const jsonStr = JSON.stringify(artifact, null, 2);
|
|
7664
7828
|
if (opts.out) {
|
|
7665
|
-
await writeTextFile(
|
|
7829
|
+
await writeTextFile(path22.resolve(opts.out), jsonStr);
|
|
7666
7830
|
console.log(chalk6.green("\u2714") + ` JSON written to ${opts.out}`);
|
|
7667
7831
|
} else {
|
|
7668
7832
|
console.log(jsonStr);
|
|
@@ -7671,7 +7835,7 @@ async function runScan(rootDir, opts) {
|
|
|
7671
7835
|
const sarif = formatSarif(artifact);
|
|
7672
7836
|
const sarifStr = JSON.stringify(sarif, null, 2);
|
|
7673
7837
|
if (opts.out) {
|
|
7674
|
-
await writeTextFile(
|
|
7838
|
+
await writeTextFile(path22.resolve(opts.out), sarifStr);
|
|
7675
7839
|
console.log(chalk6.green("\u2714") + ` SARIF written to ${opts.out}`);
|
|
7676
7840
|
} else {
|
|
7677
7841
|
console.log(sarifStr);
|
|
@@ -7680,14 +7844,14 @@ async function runScan(rootDir, opts) {
|
|
|
7680
7844
|
const text = formatText(artifact);
|
|
7681
7845
|
console.log(text);
|
|
7682
7846
|
if (opts.out) {
|
|
7683
|
-
await writeTextFile(
|
|
7847
|
+
await writeTextFile(path22.resolve(opts.out), text);
|
|
7684
7848
|
}
|
|
7685
7849
|
}
|
|
7686
7850
|
return artifact;
|
|
7687
7851
|
}
|
|
7688
7852
|
async function buildRepositoryInfo(rootDir, remoteUrl, ciSystems) {
|
|
7689
|
-
const packageJsonPath =
|
|
7690
|
-
let name =
|
|
7853
|
+
const packageJsonPath = path22.join(rootDir, "package.json");
|
|
7854
|
+
let name = path22.basename(rootDir);
|
|
7691
7855
|
let version;
|
|
7692
7856
|
if (await pathExists(packageJsonPath)) {
|
|
7693
7857
|
try {
|
|
@@ -7779,7 +7943,7 @@ function parseNonNegativeNumber(value, label) {
|
|
|
7779
7943
|
return parsed;
|
|
7780
7944
|
}
|
|
7781
7945
|
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 =
|
|
7946
|
+
const rootDir = path22.resolve(targetPath);
|
|
7783
7947
|
if (!await pathExists(rootDir)) {
|
|
7784
7948
|
console.error(chalk6.red(`Path does not exist: ${rootDir}`));
|
|
7785
7949
|
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-XPDOGGRY.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-EOULHF5E.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-37IN66KS.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