@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.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  baselineCommand,
3
3
  runBaseline
4
- } from "./chunk-IHDUX5MC.js";
5
- import "./chunk-DMYMJUQP.js";
4
+ } from "./chunk-DGQCRO6X.js";
5
+ import "./chunk-5DTDDZHE.js";
6
6
  import "./chunk-TBE6NQ5Z.js";
7
7
  export {
8
8
  baselineCommand,
@@ -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 dbEvidence = [
7821
- ...extract(file.content, /\b(?:postgres|postgresql|mysql|mariadb|mssql|sqlserver|oracle|sqlite|mongodb|redis|cassandra|dynamodb|neo4j)\b/gi, file.relPath),
7822
- ...connStrings
7823
- ];
7824
- for (const evidence of dbEvidence) {
7825
- const detected = detectDbBrand(evidence);
7826
- if (detected) dbTechnologies.push(detected);
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) dedupDb.set(`${db.kind}:${db.brand}:${db.evidence}`, db);
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 endpoints = extract(file.content, /\bhttps?:\/\/[^\s'"`]+/gi, file.relPath);
7893
- for (const endpoint of endpoints) {
7894
- const provider = endpoint.replace(/^https?:\/\//, "").split("/")[0] ?? "unknown";
7895
- const versionMatch = endpoint.match(/\/(v\d+(?:\.\d+)?)\b/i);
7896
- const params = endpoint.match(/[?&]([a-zA-Z0-9_.-]+)=/g)?.map((p) => p.replace(/[?&=]/g, "")) ?? [];
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 = `${integration.provider}:${integration.endpoint}`;
7919
- integrationMap.set(key, {
7920
- ...integration,
7921
- parameters: uniq(integration.parameters),
7922
- configOptions: uniq(integration.configOptions),
7923
- authHints: uniq(integration.authHints)
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 files = await scanTextFiles(rootDir, fileCache);
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
- const projectDir = path24.resolve(rootDir, project.path);
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);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runScan
3
- } from "./chunk-DMYMJUQP.js";
3
+ } from "./chunk-5DTDDZHE.js";
4
4
  import {
5
5
  writeJsonFile
6
6
  } from "./chunk-TBE6NQ5Z.js";
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  baselineCommand
4
- } from "./chunk-IHDUX5MC.js";
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-DMYMJUQP.js";
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-IEG2JITU.js");
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
@@ -5,7 +5,7 @@ import {
5
5
  formatText,
6
6
  generateFindings,
7
7
  runScan
8
- } from "./chunk-DMYMJUQP.js";
8
+ } from "./chunk-5DTDDZHE.js";
9
9
  import "./chunk-TBE6NQ5Z.js";
10
10
  export {
11
11
  computeDriftScore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibgrate/cli",
3
- "version": "1.0.66",
3
+ "version": "1.0.68",
4
4
  "description": "CLI for measuring upgrade drift across Node, .NET, Python & Java projects",
5
5
  "type": "module",
6
6
  "bin": {