devcompass 1.0.5 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "devcompass",
3
- "version": "1.0.5",
4
- "description": "Analyze your JavaScript projects for unused dependencies and outdated packages",
3
+ "version": "2.2.0",
4
+ "description": "Dependency health checker with ecosystem intelligence for JavaScript/TypeScript projects",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
7
  "devcompass": "./bin/devcompass.js"
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "bin/",
11
11
  "src/",
12
+ "data/",
12
13
  "README.md",
13
14
  "LICENSE"
14
15
  ],
@@ -25,7 +26,14 @@
25
26
  "cli",
26
27
  "devtools",
27
28
  "package-manager",
28
- "dependency-analysis"
29
+ "dependency-analysis",
30
+ "security",
31
+ "ecosystem",
32
+ "alerts",
33
+ "ci-cd",
34
+ "automation",
35
+ "caching",
36
+ "json-output"
29
37
  ],
30
38
  "author": "Ajay Thorat <ajaythorat988@gmail.com>",
31
39
  "license": "MIT",
@@ -34,7 +42,8 @@
34
42
  "commander": "^11.1.0",
35
43
  "depcheck": "^1.4.7",
36
44
  "npm-check-updates": "^16.14.12",
37
- "ora": "^5.4.1"
45
+ "ora": "^5.4.1",
46
+ "semver": "^7.6.0"
38
47
  },
39
48
  "engines": {
40
49
  "node": ">=14.0.0"
@@ -47,4 +56,4 @@
47
56
  "url": "https://github.com/AjayBThorat-20/devcompass/issues"
48
57
  },
49
58
  "homepage": "https://github.com/AjayBThorat-20/devcompass#readme"
50
- }
59
+ }
@@ -0,0 +1,69 @@
1
+ // src/alerts/formatter.js
2
+ const chalk = require('chalk');
3
+
4
+ /**
5
+ * Format alerts for terminal output
6
+ */
7
+ function formatAlerts(alerts) {
8
+ if (alerts.length === 0) {
9
+ return null;
10
+ }
11
+
12
+ // Group alerts by package
13
+ const grouped = {};
14
+
15
+ alerts.forEach(alert => {
16
+ if (!grouped[alert.package]) {
17
+ grouped[alert.package] = [];
18
+ }
19
+ grouped[alert.package].push(alert);
20
+ });
21
+
22
+ return grouped;
23
+ }
24
+
25
+ /**
26
+ * Get severity emoji and color
27
+ */
28
+ function getSeverityDisplay(severity) {
29
+ const displays = {
30
+ critical: { emoji: '🔴', color: chalk.red.bold, label: 'CRITICAL' },
31
+ high: { emoji: '🟠', color: chalk.red, label: 'HIGH' },
32
+ medium: { emoji: '🟡', color: chalk.yellow, label: 'MEDIUM' },
33
+ low: { emoji: '⚪', color: chalk.gray, label: 'LOW' }
34
+ };
35
+
36
+ return displays[severity] || displays.medium;
37
+ }
38
+
39
+ /**
40
+ * Calculate alert impact on health score
41
+ */
42
+ function calculateAlertPenalty(alerts) {
43
+ let penalty = 0;
44
+
45
+ alerts.forEach(alert => {
46
+ switch (alert.severity) {
47
+ case 'critical':
48
+ penalty += 2.0;
49
+ break;
50
+ case 'high':
51
+ penalty += 1.5;
52
+ break;
53
+ case 'medium':
54
+ penalty += 0.5;
55
+ break;
56
+ case 'low':
57
+ penalty += 0.2;
58
+ break;
59
+ }
60
+ });
61
+
62
+ return Math.min(penalty, 5.0); // Cap at 5 points max
63
+ }
64
+
65
+ module.exports = {
66
+ formatAlerts,
67
+ getSeverityDisplay,
68
+ calculateAlertPenalty
69
+ };
@@ -0,0 +1,32 @@
1
+ // src/alerts/index.js
2
+ const { matchIssues } = require('./matcher');
3
+ const { formatAlerts } = require('./formatter');
4
+ const { resolveInstalledVersions } = require('./resolver');
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+
8
+ async function checkEcosystemAlerts(projectPath, dependencies) {
9
+ try {
10
+ // Load issues database
11
+ const issuesDbPath = path.join(__dirname, '../../data/issues-db.json');
12
+
13
+ if (!fs.existsSync(issuesDbPath)) {
14
+ return []; // No alerts if database doesn't exist
15
+ }
16
+
17
+ const issuesDb = JSON.parse(fs.readFileSync(issuesDbPath, 'utf8'));
18
+
19
+ // Resolve installed versions from node_modules
20
+ const installedVersions = await resolveInstalledVersions(projectPath, dependencies);
21
+
22
+ // Match issues against installed versions
23
+ const alerts = matchIssues(installedVersions, issuesDb);
24
+
25
+ return alerts;
26
+ } catch (error) {
27
+ console.error('Error in checkEcosystemAlerts:', error.message);
28
+ return []; // Return empty array on error, don't break analysis
29
+ }
30
+ }
31
+
32
+ module.exports = { checkEcosystemAlerts };
@@ -0,0 +1,51 @@
1
+ // src/alerts/matcher.js
2
+ const semver = require('semver');
3
+
4
+ /**
5
+ * Match installed packages against known issues database
6
+ */
7
+ function matchIssues(installedVersions, issuesDb) {
8
+ const alerts = [];
9
+
10
+ for (const [packageName, versionInfo] of Object.entries(installedVersions)) {
11
+ // Check if this package has known issues
12
+ if (!issuesDb[packageName]) {
13
+ continue;
14
+ }
15
+
16
+ const packageIssues = issuesDb[packageName];
17
+ const installedVersion = versionInfo.version;
18
+
19
+ // Check each issue for this package
20
+ for (const issue of packageIssues) {
21
+ try {
22
+ // Use semver to check if installed version is affected
23
+ if (semver.satisfies(installedVersion, issue.affected)) {
24
+ alerts.push({
25
+ package: packageName,
26
+ version: installedVersion,
27
+ severity: issue.severity,
28
+ title: issue.title,
29
+ affected: issue.affected,
30
+ fix: issue.fix || null,
31
+ source: issue.source || null,
32
+ reported: issue.reported || null
33
+ });
34
+ }
35
+ } catch (error) {
36
+ // Skip if semver parsing fails
37
+ continue;
38
+ }
39
+ }
40
+ }
41
+
42
+ // Sort by severity: critical > high > medium > low
43
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
44
+ alerts.sort((a, b) => {
45
+ return severityOrder[a.severity] - severityOrder[b.severity];
46
+ });
47
+
48
+ return alerts;
49
+ }
50
+
51
+ module.exports = { matchIssues };
@@ -0,0 +1,46 @@
1
+ // src/alerts/resolver.js
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ /**
6
+ * Resolve actual installed versions from node_modules
7
+ * This is CRITICAL - we need installed version, not package.json version
8
+ */
9
+ async function resolveInstalledVersions(projectPath, dependencies) {
10
+ const installedVersions = {};
11
+
12
+ for (const [packageName, declaredVersion] of Object.entries(dependencies)) {
13
+ try {
14
+ const packageJsonPath = path.join(
15
+ projectPath,
16
+ 'node_modules',
17
+ packageName,
18
+ 'package.json'
19
+ );
20
+
21
+ if (fs.existsSync(packageJsonPath)) {
22
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
23
+ installedVersions[packageName] = {
24
+ name: packageName,
25
+ version: packageJson.version, // Clean version like "1.6.0"
26
+ declaredVersion: declaredVersion // What's in package.json like "^1.6.0"
27
+ };
28
+ } else {
29
+ // Fallback: use declared version (strip prefixes)
30
+ const cleanVersion = declaredVersion.replace(/^[\^~>=<]/, '');
31
+ installedVersions[packageName] = {
32
+ name: packageName,
33
+ version: cleanVersion,
34
+ declaredVersion: declaredVersion
35
+ };
36
+ }
37
+ } catch (error) {
38
+ // Skip packages that can't be resolved
39
+ continue;
40
+ }
41
+ }
42
+
43
+ return installedVersions;
44
+ }
45
+
46
+ module.exports = { resolveInstalledVersions };
@@ -1,3 +1,5 @@
1
+ // src/analyzers/outdated.js
2
+
1
3
  const ncu = require('npm-check-updates');
2
4
  const path = require('path');
3
5
 
@@ -1,4 +1,5 @@
1
- function calculateScore(totalDeps, unusedCount, outdatedCount) {
1
+ // src/analyzers/scoring.js
2
+ function calculateScore(totalDeps, unusedCount, outdatedCount, alertsCount = 0, alertPenalty = 0) {
2
3
  let score = 10;
3
4
 
4
5
  if (totalDeps === 0) {
@@ -7,20 +8,28 @@ function calculateScore(totalDeps, unusedCount, outdatedCount) {
7
8
  breakdown: {
8
9
  unused: 0,
9
10
  outdated: 0,
11
+ alerts: 0,
10
12
  unusedPenalty: 0,
11
- outdatedPenalty: 0
13
+ outdatedPenalty: 0,
14
+ alertsPenalty: 0
12
15
  }
13
16
  };
14
17
  }
15
18
 
19
+ // Unused dependencies penalty
16
20
  const unusedRatio = unusedCount / totalDeps;
17
21
  const unusedPenalty = unusedRatio * 4;
18
22
  score -= unusedPenalty;
19
23
 
24
+ // Outdated packages penalty
20
25
  const outdatedRatio = outdatedCount / totalDeps;
21
26
  const outdatedPenalty = outdatedRatio * 3;
22
27
  score -= outdatedPenalty;
23
28
 
29
+ // Ecosystem alerts penalty (from formatter.calculateAlertPenalty)
30
+ score -= alertPenalty;
31
+
32
+ // Ensure score is between 0 and 10
24
33
  score = Math.max(0, Math.min(10, score));
25
34
 
26
35
  return {
@@ -28,8 +37,10 @@ function calculateScore(totalDeps, unusedCount, outdatedCount) {
28
37
  breakdown: {
29
38
  unused: unusedCount,
30
39
  outdated: outdatedCount,
40
+ alerts: alertsCount,
31
41
  unusedPenalty: parseFloat(unusedPenalty.toFixed(1)),
32
- outdatedPenalty: parseFloat(outdatedPenalty.toFixed(1))
42
+ outdatedPenalty: parseFloat(outdatedPenalty.toFixed(1)),
43
+ alertsPenalty: parseFloat(alertPenalty.toFixed(1))
33
44
  }
34
45
  };
35
46
  }
@@ -1,3 +1,4 @@
1
+ // src/analyzers/unused-deps.js
1
2
  const depcheck = require('depcheck');
2
3
 
3
4
  async function findUnusedDeps(projectPath, dependencies) {
@@ -0,0 +1,90 @@
1
+ // src/cache/manager.js
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const CACHE_FILE = '.devcompass-cache.json';
6
+ const CACHE_DURATION = 3600000; // 1 hour in milliseconds
7
+
8
+ /**
9
+ * Load cache from disk
10
+ */
11
+ function loadCache(projectPath) {
12
+ try {
13
+ const cachePath = path.join(projectPath, CACHE_FILE);
14
+
15
+ if (!fs.existsSync(cachePath)) {
16
+ return {};
17
+ }
18
+
19
+ const cacheData = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
20
+ return cacheData;
21
+ } catch (error) {
22
+ return {};
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Save cache to disk
28
+ */
29
+ function saveCache(projectPath, cacheData) {
30
+ try {
31
+ const cachePath = path.join(projectPath, CACHE_FILE);
32
+ fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2), 'utf8');
33
+ } catch (error) {
34
+ // Silent fail - caching is not critical
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Get cached data if still valid
40
+ */
41
+ function getCached(projectPath, key) {
42
+ const cache = loadCache(projectPath);
43
+
44
+ if (!cache[key]) {
45
+ return null;
46
+ }
47
+
48
+ const cached = cache[key];
49
+ const age = Date.now() - cached.timestamp;
50
+
51
+ if (age > CACHE_DURATION) {
52
+ return null; // Expired
53
+ }
54
+
55
+ return cached.data;
56
+ }
57
+
58
+ /**
59
+ * Set cache entry
60
+ */
61
+ function setCache(projectPath, key, data) {
62
+ const cache = loadCache(projectPath);
63
+
64
+ cache[key] = {
65
+ timestamp: Date.now(),
66
+ data: data
67
+ };
68
+
69
+ saveCache(projectPath, cache);
70
+ }
71
+
72
+ /**
73
+ * Clear all cache
74
+ */
75
+ function clearCache(projectPath) {
76
+ try {
77
+ const cachePath = path.join(projectPath, CACHE_FILE);
78
+ if (fs.existsSync(cachePath)) {
79
+ fs.unlinkSync(cachePath);
80
+ }
81
+ } catch (error) {
82
+ // Silent fail
83
+ }
84
+ }
85
+
86
+ module.exports = {
87
+ getCached,
88
+ setCache,
89
+ clearCache
90
+ };