@vibgrate/cli 1.0.66 → 1.0.68
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.
|
@@ -319,20 +319,6 @@ function formatText(artifact) {
|
|
|
319
319
|
if (artifact.extended?.architecture) {
|
|
320
320
|
lines.push(...formatArchitectureDiagram(artifact.extended.architecture));
|
|
321
321
|
}
|
|
322
|
-
if (artifact.relationshipDiagram?.mermaid) {
|
|
323
|
-
lines.push(chalk.bold.cyan("\u2554\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\u2557"));
|
|
324
|
-
lines.push(chalk.bold.cyan("\u2551 Project Relationship Diagram \u2551"));
|
|
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
|
-
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(" ```"));
|
|
334
|
-
lines.push("");
|
|
335
|
-
}
|
|
336
322
|
if (artifact.solutions && artifact.solutions.length > 0) {
|
|
337
323
|
lines.push(chalk.bold.cyan("\u2554\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\u2557"));
|
|
338
324
|
lines.push(chalk.bold.cyan("\u2551 Solution Drift Summary \u2551"));
|
|
@@ -1308,7 +1294,9 @@ function compactApiSurface(result) {
|
|
|
1308
1294
|
parameters: [],
|
|
1309
1295
|
// Don't include params
|
|
1310
1296
|
configOptions: [],
|
|
1311
|
-
authHints: []
|
|
1297
|
+
authHints: [],
|
|
1298
|
+
files: []
|
|
1299
|
+
// Don't include file paths
|
|
1312
1300
|
}));
|
|
1313
1301
|
return {
|
|
1314
1302
|
integrations: uniqueIntegrations,
|
|
@@ -7726,6 +7714,17 @@ function extract(content, pattern, sourceFile) {
|
|
|
7726
7714
|
}
|
|
7727
7715
|
return out;
|
|
7728
7716
|
}
|
|
7717
|
+
var LOCKFILE_NAMES = /* @__PURE__ */ new Set([
|
|
7718
|
+
"package-lock.json",
|
|
7719
|
+
"pnpm-lock.yaml",
|
|
7720
|
+
"yarn.lock",
|
|
7721
|
+
"composer.lock",
|
|
7722
|
+
"Gemfile.lock",
|
|
7723
|
+
"Cargo.lock",
|
|
7724
|
+
"poetry.lock",
|
|
7725
|
+
"Pipfile.lock",
|
|
7726
|
+
"packages.lock.json"
|
|
7727
|
+
]);
|
|
7729
7728
|
function detectDbBrand(raw) {
|
|
7730
7729
|
const value = raw.toLowerCase();
|
|
7731
7730
|
if (value.includes("postgres")) return { kind: "sql", brand: "PostgreSQL", version: null, evidence: raw };
|
|
@@ -7736,6 +7735,7 @@ function detectDbBrand(raw) {
|
|
|
7736
7735
|
if (value.includes("mongodb")) return { kind: "nosql", brand: "MongoDB", version: null, evidence: raw };
|
|
7737
7736
|
if (value.includes("redis")) return { kind: "nosql", brand: "Redis", version: null, evidence: raw };
|
|
7738
7737
|
if (value.includes("cassandra")) return { kind: "nosql", brand: "Cassandra", version: null, evidence: raw };
|
|
7738
|
+
if (value.includes("cosmosdb") || value.includes("@azure/cosmos") || value.includes("cosmos")) return { kind: "nosql", brand: "CosmosDB", version: null, evidence: raw };
|
|
7739
7739
|
if (value.includes("dynamodb")) return { kind: "nosql", brand: "DynamoDB", version: null, evidence: raw };
|
|
7740
7740
|
if (value.includes("neo4j")) return { kind: "nosql", brand: "Neo4j", version: null, evidence: raw };
|
|
7741
7741
|
return null;
|
|
@@ -7817,13 +7817,16 @@ async function scanDataStores(rootDir, fileCache) {
|
|
|
7817
7817
|
for (const file of files) {
|
|
7818
7818
|
const connStrings = extract(file.content, /\b(?:postgres(?:ql)?:\/\/[^\s'"`]+|mysql:\/\/[^\s'"`]+|mongodb(?:\+srv)?:\/\/[^\s'"`]+|redis:\/\/[^\s'"`]+)\b/gi, file.relPath);
|
|
7819
7819
|
result.connectionStrings.push(...connStrings);
|
|
7820
|
-
const
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7820
|
+
const isLockFile = LOCKFILE_NAMES.has(path23.basename(file.relPath));
|
|
7821
|
+
if (!isLockFile) {
|
|
7822
|
+
const dbEvidence = [
|
|
7823
|
+
...extract(file.content, /\b(?:postgres|postgresql|mysql|mariadb|mssql|sqlserver|oracle|sqlite|mongodb|redis|cassandra|cosmosdb|cosmos|dynamodb|neo4j)\b/gi, file.relPath),
|
|
7824
|
+
...connStrings
|
|
7825
|
+
];
|
|
7826
|
+
for (const evidence of dbEvidence) {
|
|
7827
|
+
const detected = detectDbBrand(evidence);
|
|
7828
|
+
if (detected) dbTechnologies.push(detected);
|
|
7829
|
+
}
|
|
7827
7830
|
}
|
|
7828
7831
|
result.connectionPoolSettings.push(...extract(file.content, /\b(?:pool(?:Size|_size)?|maxPoolSize|minPoolSize|connectionLimit)\b[^\n]*/gi, file.relPath));
|
|
7829
7832
|
result.replicationSettings.push(...extract(file.content, /\b(?:replication|cluster|replicaSet)\b[^\n]*/gi, file.relPath));
|
|
@@ -7840,7 +7843,10 @@ async function scanDataStores(rootDir, fileCache) {
|
|
|
7840
7843
|
result.otherServices.push(...extract(file.content, /\b(?:redis:\/\/[^\s'"`]+|amqp:\/\/[^\s'"`]+|kafka:\/\/[^\s'"`]+|elasticsearch:\/\/[^\s'"`]+)\b/gi, file.relPath));
|
|
7841
7844
|
}
|
|
7842
7845
|
const dedupDb = /* @__PURE__ */ new Map();
|
|
7843
|
-
for (const db of dbTechnologies)
|
|
7846
|
+
for (const db of dbTechnologies) {
|
|
7847
|
+
const key = `${db.kind}:${db.brand}`;
|
|
7848
|
+
if (!dedupDb.has(key)) dedupDb.set(key, db);
|
|
7849
|
+
}
|
|
7844
7850
|
result.databaseTechnologies = [...dedupDb.values()].sort((a, b) => a.brand.localeCompare(b.brand));
|
|
7845
7851
|
result.connectionStrings = uniq(result.connectionStrings);
|
|
7846
7852
|
result.connectionPoolSettings = uniq(result.connectionPoolSettings);
|
|
@@ -7870,6 +7876,17 @@ function detectOpenApiSpecification(relPath, content) {
|
|
|
7870
7876
|
const endpointCount = content.match(/(^|\n)\s*\/[^\n:]+:\s*(get|post|put|patch|delete|options|head)/gim)?.length ?? content.match(/"\/[^"]+"\s*:\s*\{/g)?.length ?? null;
|
|
7871
7877
|
return { path: relPath, format, version, title, endpointCount };
|
|
7872
7878
|
}
|
|
7879
|
+
function isInternalHost(hostname) {
|
|
7880
|
+
const h = hostname.toLowerCase();
|
|
7881
|
+
if (h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0") return true;
|
|
7882
|
+
if (/^192\.168\./.test(h) || /^10\./.test(h) || /^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
|
|
7883
|
+
if (!h.includes(".")) return true;
|
|
7884
|
+
if (/^(example|test|localhost|local|internal)\b/.test(h)) return true;
|
|
7885
|
+
return false;
|
|
7886
|
+
}
|
|
7887
|
+
function normalizeUrl(raw) {
|
|
7888
|
+
return raw.replace(/[\u0000-\u001F\u007F-\u009F\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]/g, "").trim();
|
|
7889
|
+
}
|
|
7873
7890
|
async function scanApiSurface(rootDir, fileCache) {
|
|
7874
7891
|
const files = await scanTextFiles(rootDir, fileCache);
|
|
7875
7892
|
const integrations = [];
|
|
@@ -7889,18 +7906,29 @@ async function scanApiSurface(rootDir, fileCache) {
|
|
|
7889
7906
|
for (const file of files) {
|
|
7890
7907
|
const openApiSpec = detectOpenApiSpecification(file.relPath, file.content);
|
|
7891
7908
|
if (openApiSpec) result.openApiSpecifications.push(openApiSpec);
|
|
7892
|
-
const
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
const
|
|
7896
|
-
const
|
|
7909
|
+
const urlRegex = /\bhttps?:\/\/[^\s'"`]+/gi;
|
|
7910
|
+
let match;
|
|
7911
|
+
while ((match = urlRegex.exec(file.content)) !== null) {
|
|
7912
|
+
const rawUrl = match[0].replace(/[,;)\]}"'`]+$/, "");
|
|
7913
|
+
const cleanUrl = normalizeUrl(rawUrl);
|
|
7914
|
+
if (!cleanUrl.startsWith("http")) continue;
|
|
7915
|
+
let hostname;
|
|
7916
|
+
try {
|
|
7917
|
+
hostname = new URL(cleanUrl).hostname;
|
|
7918
|
+
} catch {
|
|
7919
|
+
continue;
|
|
7920
|
+
}
|
|
7921
|
+
if (isInternalHost(hostname)) continue;
|
|
7922
|
+
const versionMatch = cleanUrl.match(/\/(v\d+(?:\.\d+)?)\b/i);
|
|
7923
|
+
const params = cleanUrl.match(/[?&]([a-zA-Z0-9_.-]+)=/g)?.map((p) => p.replace(/[?&=]/g, "")) ?? [];
|
|
7897
7924
|
integrations.push({
|
|
7898
|
-
provider,
|
|
7899
|
-
endpoint,
|
|
7925
|
+
provider: hostname,
|
|
7926
|
+
endpoint: cleanUrl,
|
|
7900
7927
|
version: versionMatch ? versionMatch[1] : null,
|
|
7901
7928
|
parameters: params,
|
|
7902
7929
|
configOptions: extract(file.content, /\b(?:baseUrl|apiUrl|endpoint|timeout|retries|apiVersion)\b[^\n]*/gi, file.relPath),
|
|
7903
|
-
authHints: extract(file.content, /\b(?:api[_-]?key|bearer\s+[a-z0-9_.-]+|client[_-]?secret|oauth)\b[^\n]*/gi, file.relPath)
|
|
7930
|
+
authHints: extract(file.content, /\b(?:api[_-]?key|bearer\s+[a-z0-9_.-]+|client[_-]?secret|oauth)\b[^\n]*/gi, file.relPath),
|
|
7931
|
+
files: [file.relPath]
|
|
7904
7932
|
});
|
|
7905
7933
|
}
|
|
7906
7934
|
result.webhookUrls.push(...extract(file.content, /\bhttps?:\/\/[^\s'"`]*(?:webhook|hooks?)[^\s'"`]*/gi, file.relPath));
|
|
@@ -7910,18 +7938,26 @@ async function scanApiSurface(rootDir, fileCache) {
|
|
|
7910
7938
|
result.rateLimitOverrides.push(...extract(file.content, /\b(?:rate[_-]?limit|max[_-]?requests|throttle)\b[^\n]*/gi, file.relPath));
|
|
7911
7939
|
result.customHeaders.push(...extract(file.content, /\b(?:x-[a-z0-9-]+|authorization|user-agent)\b[^\n]*/gi, file.relPath));
|
|
7912
7940
|
result.corsPolicies.push(...extract(file.content, /\b(?:cors|access-control-allow-origin|allowedOrigins)\b[^\n]*/gi, file.relPath));
|
|
7913
|
-
result.oauthScopes.push(...extract(file.content, /\b(?:scope|scopes)\b[^\n]*(?:openid|profile|email|read|write)/gi, file.relPath));
|
|
7914
7941
|
result.apiTokens.push(...extract(file.content, /\b(?:api[_-]?token|access[_-]?token|bearer[_-]?token)\b[^\n]*/gi, file.relPath));
|
|
7915
7942
|
}
|
|
7916
7943
|
const integrationMap = /* @__PURE__ */ new Map();
|
|
7917
7944
|
for (const integration of integrations) {
|
|
7918
|
-
const key =
|
|
7919
|
-
integrationMap.
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7945
|
+
const key = integration.endpoint;
|
|
7946
|
+
const existing = integrationMap.get(key);
|
|
7947
|
+
if (existing) {
|
|
7948
|
+
existing.files = uniq([...existing.files, ...integration.files]);
|
|
7949
|
+
existing.parameters = uniq([...existing.parameters, ...integration.parameters]);
|
|
7950
|
+
existing.configOptions = uniq([...existing.configOptions, ...integration.configOptions]);
|
|
7951
|
+
existing.authHints = uniq([...existing.authHints, ...integration.authHints]);
|
|
7952
|
+
} else {
|
|
7953
|
+
integrationMap.set(key, {
|
|
7954
|
+
...integration,
|
|
7955
|
+
parameters: uniq(integration.parameters),
|
|
7956
|
+
configOptions: uniq(integration.configOptions),
|
|
7957
|
+
authHints: uniq(integration.authHints),
|
|
7958
|
+
files: [...integration.files]
|
|
7959
|
+
});
|
|
7960
|
+
}
|
|
7925
7961
|
}
|
|
7926
7962
|
result.integrations = [...integrationMap.values()].sort((a, b) => a.provider.localeCompare(b.provider));
|
|
7927
7963
|
result.openApiSpecifications = [...new Map(result.openApiSpecifications.map((spec) => [spec.path, spec])).values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
@@ -7936,8 +7972,27 @@ async function scanApiSurface(rootDir, fileCache) {
|
|
|
7936
7972
|
result.apiTokens = uniq(result.apiTokens);
|
|
7937
7973
|
return result;
|
|
7938
7974
|
}
|
|
7975
|
+
var NON_CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
7976
|
+
".md",
|
|
7977
|
+
".mdx",
|
|
7978
|
+
".markdown",
|
|
7979
|
+
".txt",
|
|
7980
|
+
".rst",
|
|
7981
|
+
".adoc",
|
|
7982
|
+
".asciidoc",
|
|
7983
|
+
".html",
|
|
7984
|
+
".htm",
|
|
7985
|
+
".pdf",
|
|
7986
|
+
".docx",
|
|
7987
|
+
".doc",
|
|
7988
|
+
".rtf"
|
|
7989
|
+
]);
|
|
7939
7990
|
async function scanOperationalResilience(rootDir, fileCache) {
|
|
7940
|
-
const
|
|
7991
|
+
const allFiles = await scanTextFiles(rootDir, fileCache);
|
|
7992
|
+
const files = allFiles.filter((f) => {
|
|
7993
|
+
const ext = path23.extname(f.relPath).toLowerCase();
|
|
7994
|
+
return !NON_CODE_EXTENSIONS.has(ext);
|
|
7995
|
+
});
|
|
7941
7996
|
const result = {
|
|
7942
7997
|
implicitTimeouts: [],
|
|
7943
7998
|
defaultPaginationSize: [],
|
|
@@ -8025,6 +8080,19 @@ async function scanAssetBranding(rootDir, fileCache) {
|
|
|
8025
8080
|
productLogos: uniq(logoCandidates.map((entry) => entry.relPath))
|
|
8026
8081
|
};
|
|
8027
8082
|
}
|
|
8083
|
+
async function findProjectFavicon(projectDir) {
|
|
8084
|
+
const cache = new FileCache();
|
|
8085
|
+
try {
|
|
8086
|
+
const entries = await cache.walkDir(projectDir);
|
|
8087
|
+
const favicon = entries.find((entry) => entry.isFile && /favicon/i.test(entry.name));
|
|
8088
|
+
if (!favicon) return null;
|
|
8089
|
+
const content = await cache.readTextFile(favicon.absPath);
|
|
8090
|
+
if (!content) return null;
|
|
8091
|
+
return Buffer.from(content).toString("base64").slice(0, 1e4);
|
|
8092
|
+
} catch {
|
|
8093
|
+
return null;
|
|
8094
|
+
}
|
|
8095
|
+
}
|
|
8028
8096
|
async function scanOssGovernance(rootDir, fileCache) {
|
|
8029
8097
|
const files = await scanTextFiles(rootDir, fileCache);
|
|
8030
8098
|
const directDeps = /* @__PURE__ */ new Set();
|
|
@@ -8765,6 +8833,11 @@ async function runScan(rootDir, opts) {
|
|
|
8765
8833
|
}
|
|
8766
8834
|
}));
|
|
8767
8835
|
}
|
|
8836
|
+
await Promise.all(allProjects.map(async (project) => {
|
|
8837
|
+
const projectAbsPath = path24.join(rootDir, project.path);
|
|
8838
|
+
const favicon = await findProjectFavicon(projectAbsPath);
|
|
8839
|
+
if (favicon) project.faviconBase64 = favicon;
|
|
8840
|
+
}));
|
|
8768
8841
|
if (scannerPolicy.architecture && scanners?.architecture?.enabled !== false) {
|
|
8769
8842
|
progress.startStep("architecture");
|
|
8770
8843
|
extended.architecture = await scanArchitecture(
|
|
@@ -8912,12 +8985,10 @@ async function runScan(rootDir, opts) {
|
|
|
8912
8985
|
steps: progress.getStepTimings()
|
|
8913
8986
|
});
|
|
8914
8987
|
if (!opts.noLocalArtifacts && !maxPrivacyMode) {
|
|
8988
|
+
const projectScores = {};
|
|
8915
8989
|
for (const project of allProjects) {
|
|
8916
8990
|
if (project.drift && project.path) {
|
|
8917
|
-
|
|
8918
|
-
const projectVibgrateDir = path24.join(projectDir, ".vibgrate");
|
|
8919
|
-
await ensureDir(projectVibgrateDir);
|
|
8920
|
-
await writeJsonFile(path24.join(projectVibgrateDir, "project_score.json"), {
|
|
8991
|
+
projectScores[project.path] = {
|
|
8921
8992
|
projectId: project.projectId,
|
|
8922
8993
|
name: project.name,
|
|
8923
8994
|
type: project.type,
|
|
@@ -8930,9 +9001,14 @@ async function runScan(rootDir, opts) {
|
|
|
8930
9001
|
vibgrateVersion: VERSION,
|
|
8931
9002
|
solutionId: project.solutionId,
|
|
8932
9003
|
solutionName: project.solutionName
|
|
8933
|
-
}
|
|
9004
|
+
};
|
|
8934
9005
|
}
|
|
8935
9006
|
}
|
|
9007
|
+
if (Object.keys(projectScores).length > 0) {
|
|
9008
|
+
const vibgrateDir = path24.join(rootDir, ".vibgrate");
|
|
9009
|
+
await ensureDir(vibgrateDir);
|
|
9010
|
+
await writeJsonFile(path24.join(vibgrateDir, "project_scores.json"), projectScores);
|
|
9011
|
+
}
|
|
8936
9012
|
}
|
|
8937
9013
|
if (opts.format === "json") {
|
|
8938
9014
|
const jsonStr = JSON.stringify(artifact, null, 2);
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
baselineCommand
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DGQCRO6X.js";
|
|
5
5
|
import {
|
|
6
6
|
VERSION,
|
|
7
7
|
dsnCommand,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
pushCommand,
|
|
11
11
|
scanCommand,
|
|
12
12
|
writeDefaultConfig
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-5DTDDZHE.js";
|
|
14
14
|
import {
|
|
15
15
|
ensureDir,
|
|
16
16
|
pathExists,
|
|
@@ -39,7 +39,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
39
39
|
console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
|
|
40
40
|
}
|
|
41
41
|
if (opts.baseline) {
|
|
42
|
-
const { runBaseline } = await import("./baseline-
|
|
42
|
+
const { runBaseline } = await import("./baseline-ZTTNU7WC.js");
|
|
43
43
|
await runBaseline(rootDir);
|
|
44
44
|
}
|
|
45
45
|
console.log("");
|
package/dist/index.d.ts
CHANGED
|
@@ -59,6 +59,8 @@ interface ProjectScan {
|
|
|
59
59
|
relationshipDiagram?: MermaidDiagram;
|
|
60
60
|
/** Compacted UI purpose evidence for this project */
|
|
61
61
|
uiPurpose?: CompactUiPurpose;
|
|
62
|
+
/** Base64-encoded favicon for this project, detected from its public/ directory */
|
|
63
|
+
faviconBase64?: string;
|
|
62
64
|
}
|
|
63
65
|
interface SolutionScan {
|
|
64
66
|
/** Deterministic solution ID: SHA-256 hash of `${path}:${name}:${workspaceId}` */
|
|
@@ -541,6 +543,7 @@ interface ApiIntegration {
|
|
|
541
543
|
parameters: string[];
|
|
542
544
|
configOptions: string[];
|
|
543
545
|
authHints: string[];
|
|
546
|
+
files: string[];
|
|
544
547
|
}
|
|
545
548
|
interface ApiSurfaceResult {
|
|
546
549
|
integrations: ApiIntegration[];
|
package/dist/index.js
CHANGED