licenseguard-cli 2.1.0 ā 2.1.1
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/CHANGELOG.md +210 -0
- package/README.md +66 -0
- package/bin/licenseguard.js +25 -0
- package/lib/commands/scan.js +3 -2
- package/lib/scanner/color-mapper.js +87 -0
- package/lib/scanner/index.js +8 -2
- package/lib/scanner/license-detector.js +1 -1
- package/lib/scanner/plugins/go.js +0 -1
- package/lib/scanner/plugins/python.js +1 -1
- package/lib/utils/update-notifier.js +141 -0
- package/package.json +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [2.1.1] - 2025-11-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Color-coded License Output** - Visual safety hierarchy for quick risk assessment
|
|
12
|
+
- Green (š¢): Permissive licenses (MIT, Apache-2.0, BSD-*, ISC)
|
|
13
|
+
- Yellow (ā ļø): Weak copyleft (MPL-2.0, LGPL-*)
|
|
14
|
+
- Red (ā): Strong copyleft (GPL-*, AGPL-*)
|
|
15
|
+
- Gray (ā): Unknown licenses (requires manual review)
|
|
16
|
+
- Emojis as secondary indicators for accessibility (colorblind-friendly)
|
|
17
|
+
- Works in both light and dark terminal themes
|
|
18
|
+
- **Update Notifier** - Automatic update notifications
|
|
19
|
+
- Checks npm registry once per 24 hours
|
|
20
|
+
- Displays banner when newer version available
|
|
21
|
+
- Non-blocking (doesn't slow down CLI startup)
|
|
22
|
+
- Fails silently if network unavailable
|
|
23
|
+
- Cache stored in OS temp directory
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- All license output now color-coded in `init` command
|
|
27
|
+
- Conflict reports now show visual safety indicators
|
|
28
|
+
|
|
29
|
+
## [2.1.0] - 2025-11-23
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
#### Multi-Ecosystem Dependency Scanning
|
|
34
|
+
- **C/C++ (Conan) Support**
|
|
35
|
+
- Scans Conan 2.x and 1.x projects
|
|
36
|
+
- Auto-detects `conanfile.txt` and `conanfile.py`
|
|
37
|
+
- Tested with Facebook's folly library (23 dependencies, found 3 real GPL conflicts)
|
|
38
|
+
- Prevents GPL contamination in MIT/Apache projects
|
|
39
|
+
- **Rust (Cargo) Support**
|
|
40
|
+
- Scans Cargo projects via `cargo metadata --format-version 1`
|
|
41
|
+
- Auto-detects `Cargo.toml`
|
|
42
|
+
- 100% test coverage
|
|
43
|
+
- **Python (pip/pipenv/poetry) Support**
|
|
44
|
+
- **Native Python scanner** using `importlib.metadata` (IPC Bridge approach)
|
|
45
|
+
- **98.6% detection rate** (342/347 packages) vs 9.2% with pip show parsing
|
|
46
|
+
- Auto-detects `requirements.txt`, `Pipfile`, `pyproject.toml`
|
|
47
|
+
- Priority: poetry > pipenv > pip
|
|
48
|
+
- 37 license normalizations for Python ecosystem quirks
|
|
49
|
+
- Batch optimization (30x faster than individual calls)
|
|
50
|
+
- **Go (modules) Support**
|
|
51
|
+
- Scans Go modules with streaming NDJSON for large projects
|
|
52
|
+
- Dynamic cache detection via `go env GOMODCACHE`
|
|
53
|
+
- Auto-detects `go.mod`
|
|
54
|
+
- Jaccard Index matching for LICENSE files (no package metadata fallback)
|
|
55
|
+
|
|
56
|
+
- **Authoritative Source Citations (--explain)**
|
|
57
|
+
- Added `--explain` flag to `init` and `scan` commands
|
|
58
|
+
- Shows citations from FSF, OSI, and Mozilla for compatibility decisions
|
|
59
|
+
- Provides direct URLs to license text and compatibility matrices
|
|
60
|
+
- Helps developers verify "Why is this a conflict?" with legal backing
|
|
61
|
+
|
|
62
|
+
#### Advanced License Detection
|
|
63
|
+
- **Jaccard Index License Detector** (5-layer multi-strategy detection)
|
|
64
|
+
- Layer 1: SPDX-License-Identifier headers (fastest)
|
|
65
|
+
- Layer 2: License header/title detection (for full license texts)
|
|
66
|
+
- Layer 3: Dual-license pattern detection
|
|
67
|
+
- Layer 4: Key phrase patterns (distinctive phrases)
|
|
68
|
+
- Layer 5: Jaccard similarity matching (edge cases)
|
|
69
|
+
- Reduced Go scan warnings from 27 to 7
|
|
70
|
+
- Handles BSD-2-Clause vs BSD-3-Clause differentiation
|
|
71
|
+
- Universal detector usable across all ecosystems
|
|
72
|
+
- **GPL Contamination Prevention**
|
|
73
|
+
- Detects copyleft licenses in transitive dependency trees
|
|
74
|
+
- Real-world validation: Found 3 GPL conflicts in folly's 23-dependency tree
|
|
75
|
+
- Business value: Prevents license violations before production
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
#### Architecture
|
|
80
|
+
- **Plugin Architecture** - Refactored from monolithic to pluggable ecosystem plugins
|
|
81
|
+
- `lib/scanner/plugins/node.js` - Node.js scanner (extracted from monolithic v2.0)
|
|
82
|
+
- `lib/scanner/plugins/cpp.js` - C/C++ Conan scanner
|
|
83
|
+
- `lib/scanner/plugins/rust.js` - Rust Cargo scanner
|
|
84
|
+
- `lib/scanner/plugins/python.js` - Python scanner with IPC bridge
|
|
85
|
+
- `lib/scanner/plugins/go.js` - Go modules scanner
|
|
86
|
+
- **Auto-detection** - Scanner now auto-detects project type (Node > C++ > Rust > Python > Go priority)
|
|
87
|
+
- **Node.js Scanner** - Backward compatible, all Epic 2 tests still passing
|
|
88
|
+
|
|
89
|
+
### Technical
|
|
90
|
+
|
|
91
|
+
#### New Files
|
|
92
|
+
- `lib/scanner/plugins/cpp.js` - Conan plugin (87% coverage)
|
|
93
|
+
- `lib/scanner/plugins/rust.js` - Cargo plugin (100% coverage)
|
|
94
|
+
- `lib/scanner/plugins/python.js` - Python plugin with IPC bridge (98% coverage)
|
|
95
|
+
- `lib/scanner/plugins/python-license-scanner.py` - Native Python scanner script
|
|
96
|
+
- `lib/scanner/plugins/go.js` - Go modules plugin (92% coverage)
|
|
97
|
+
- `lib/scanner/plugins/node.js` - Refactored Node.js scanner (100% coverage)
|
|
98
|
+
- `lib/scanner/license-detector.js` - Jaccard Index multi-strategy detector (85% coverage)
|
|
99
|
+
|
|
100
|
+
#### Test Growth
|
|
101
|
+
- **635 tests** (was 132 in v2.0.0) - **+503 tests, +381% growth**
|
|
102
|
+
- **19 test suites** (was ~10 in v2.0.0)
|
|
103
|
+
- **0 regressions** - All Epic 2 tests passing
|
|
104
|
+
- **Coverage:**
|
|
105
|
+
- Plugins: 94.37% (target: >80%)
|
|
106
|
+
- License detector: 85.41% (target: >80%)
|
|
107
|
+
- Overall: 84.46% statements, 75.95% branches
|
|
108
|
+
|
|
109
|
+
#### Dependencies
|
|
110
|
+
- No new npm dependencies added (uses child_process for ecosystem tools)
|
|
111
|
+
|
|
112
|
+
#### Performance
|
|
113
|
+
- Node.js: <1s for 1500 packages
|
|
114
|
+
- Python: ~1-2s for 347 packages (IPC Bridge overhead)
|
|
115
|
+
- C++: <1s for 23 packages (Conan metadata parsing)
|
|
116
|
+
- Rust: <1s for typical project
|
|
117
|
+
- Go: <1s for typical project
|
|
118
|
+
|
|
119
|
+
### Breaking Changes
|
|
120
|
+
None - Fully backward compatible with v2.0.0
|
|
121
|
+
|
|
122
|
+
### Known Limitations
|
|
123
|
+
- Mixed-language projects not yet supported (auto-detection picks first match)
|
|
124
|
+
- Python requires Python 3.7+ installed
|
|
125
|
+
- C++ requires Conan 1.x or 2.x installed
|
|
126
|
+
- Rust requires Cargo installed
|
|
127
|
+
- Go requires Go installed
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
**Epic 3 Completed:** Multi-Ecosystem Scanner Support
|
|
132
|
+
**Stories Completed:** 3.0 (Plugin Architecture), 3.1 (C++), 3.2 (Rust), 3.3 (Python), 3.4 (Go), plus 2 hotfixes (compat-checker, license-detector)
|
|
133
|
+
|
|
134
|
+
## [2.0.0] - 2025-11-18
|
|
135
|
+
|
|
136
|
+
### BREAKING CHANGES
|
|
137
|
+
|
|
138
|
+
#### CLI Architecture Migration
|
|
139
|
+
- **Migrated from flag-based to subcommand-based routing**
|
|
140
|
+
- Old: `licenseguard --init` ā New: `licenseguard init`
|
|
141
|
+
- Old: `licenseguard --ls` ā New: `licenseguard ls`
|
|
142
|
+
- Old: `licenseguard --setup` ā New: `licenseguard setup`
|
|
143
|
+
- **Rationale:** Subcommands provide better CLI semantics and enable future extensibility
|
|
144
|
+
- **Migration:** Update all scripts and documentation to use new syntax
|
|
145
|
+
- **Backward compatibility:** Use `--noscan` flag for v1.x behavior without dependency scanning
|
|
146
|
+
|
|
147
|
+
### Added
|
|
148
|
+
|
|
149
|
+
#### License Compliance Guard
|
|
150
|
+
- **Dependency license scanning during init**
|
|
151
|
+
- Scans all npm dependencies for license conflicts
|
|
152
|
+
- Reads `package.json` and `node_modules/*/package.json`
|
|
153
|
+
- Displays scan summary with compatible/incompatible/unknown counts
|
|
154
|
+
- **SPDX license compatibility checking**
|
|
155
|
+
- Uses `spdx-satisfies` for industry-standard compatibility rules
|
|
156
|
+
- Checks copyleft vs permissive conflicts (e.g., GPL-3.0 incompatible with MIT)
|
|
157
|
+
- Supports complex license expressions (e.g., "MIT OR Apache-2.0")
|
|
158
|
+
- **Conflict detection with blocking**
|
|
159
|
+
- LICENSE creation blocked if incompatible licenses detected
|
|
160
|
+
- Exits with code 1 and error message
|
|
161
|
+
- Shows detailed conflict report with package names, licenses, and reasons
|
|
162
|
+
- **scanResult persistence to .licenseguardrc**
|
|
163
|
+
- Optional field to save scan results for transparency
|
|
164
|
+
- Includes timestamp, counts, and issues array
|
|
165
|
+
- Acts as compliance badge (like CI or coverage badges)
|
|
166
|
+
- Prompt with smart defaults: YES for clean scans, NO for conflicts
|
|
167
|
+
- **--force flag to override blocking**
|
|
168
|
+
- Creates LICENSE despite conflicts when user explicitly accepts risks
|
|
169
|
+
- Shows warnings but allows proceeding
|
|
170
|
+
- Useful for false positives or acceptable conflicts
|
|
171
|
+
- **--noscan flag for v1.x compatibility**
|
|
172
|
+
- Skips dependency scanning entirely
|
|
173
|
+
- Maintains v1.x behavior for non-JavaScript projects
|
|
174
|
+
- No scanResult generated or saved
|
|
175
|
+
|
|
176
|
+
### Changed
|
|
177
|
+
|
|
178
|
+
- **Help text improved** - Now shows subcommands with descriptions
|
|
179
|
+
- **Error messages enhanced** - More actionable feedback for common issues
|
|
180
|
+
- **CLI routing refactored** - Cleaner subcommand architecture
|
|
181
|
+
- **Init command enhanced** - Integrated scanning after license selection, before file creation
|
|
182
|
+
- **Init-fast command enhanced** - Auto-saves clean scan results, skips saving on conflicts
|
|
183
|
+
- **Configuration format extended** - `.licenseguardrc` now supports optional `scanResult` field
|
|
184
|
+
|
|
185
|
+
### Technical
|
|
186
|
+
|
|
187
|
+
#### New Dependencies
|
|
188
|
+
- `spdx-satisfies@5.x` - SPDX license compatibility checking
|
|
189
|
+
- `spdx-expression-parse@4.x` - Parse SPDX license expressions
|
|
190
|
+
|
|
191
|
+
#### New Modules
|
|
192
|
+
- `lib/scanner/index.js` - Dependency scanner with conflict detection
|
|
193
|
+
- `lib/compat/rules.js` - License compatibility rules engine
|
|
194
|
+
|
|
195
|
+
#### Test Coverage
|
|
196
|
+
- Added scanner unit tests
|
|
197
|
+
- Added file-ops scanResult handling tests
|
|
198
|
+
- Maintained 86%+ coverage target
|
|
199
|
+
|
|
200
|
+
## [1.1.0] - 2025-11-17
|
|
201
|
+
|
|
202
|
+
### Added
|
|
203
|
+
- Initial public release (Epic 1)
|
|
204
|
+
- Interactive license setup (`init` command)
|
|
205
|
+
- Fast mode for CI/CD (`init --fast`)
|
|
206
|
+
- 6 embedded license templates (MIT, Apache 2.0, GPL 3.0, BSD 3-Clause, ISC, WTFPL)
|
|
207
|
+
- Git hooks for license notifications (post-checkout, pre-commit)
|
|
208
|
+
- Global hooks installation via npm postinstall
|
|
209
|
+
- `.licenseguardrc` configuration file
|
|
210
|
+
- Cross-platform support (Linux, macOS, Windows)
|
package/README.md
CHANGED
|
@@ -269,6 +269,72 @@ Reads existing `.licenseguardrc` and installs hooks. Used in npm prepare scripts
|
|
|
269
269
|
|
|
270
270
|
---
|
|
271
271
|
|
|
272
|
+
## Color-coded Output
|
|
273
|
+
|
|
274
|
+
LicenseGuard uses visual safety hierarchy with color-coding to help you quickly identify dangerous licenses:
|
|
275
|
+
|
|
276
|
+
### Safety Level Legend
|
|
277
|
+
|
|
278
|
+
| Color | Emoji | License Type | Examples |
|
|
279
|
+
|-------|-------|--------------|----------|
|
|
280
|
+
| š¢ **Green** | Safe | Permissive licenses | MIT, Apache-2.0, BSD-*, ISC, 0BSD, Unlicense |
|
|
281
|
+
| ā ļø **Yellow** | Caution | Weak copyleft | MPL-2.0, LGPL-*, EPL-* |
|
|
282
|
+
| ā **Red** | Dangerous | Strong copyleft | GPL-*, AGPL-* |
|
|
283
|
+
| ā **Gray** | Unknown | Requires manual review | UNKNOWN, unrecognized licenses |
|
|
284
|
+
|
|
285
|
+
### How It Works
|
|
286
|
+
|
|
287
|
+
Licenses are automatically color-coded in all commands (`init`, `scan`) based on their safety level:
|
|
288
|
+
|
|
289
|
+
- **Green licenses** (š¢) are permissive and safe to use - they allow you to do almost anything
|
|
290
|
+
- **Yellow licenses** (ā ļø) require caution - they have some copyleft restrictions
|
|
291
|
+
- **Red licenses** (ā) are strong copyleft - they may require your project to use the same license
|
|
292
|
+
- **Gray licenses** (ā) are unknown or unrecognized - manual review required
|
|
293
|
+
|
|
294
|
+
**Example Output:**
|
|
295
|
+
```bash
|
|
296
|
+
licenseguard scan
|
|
297
|
+
|
|
298
|
+
ā 2 issue(s) found:
|
|
299
|
+
|
|
300
|
+
ā some-gpl-lib@2.0.0 (ā GPL-3.0)
|
|
301
|
+
Conflict: Copyleft incompatible with MIT
|
|
302
|
+
Location: node_modules/some-gpl-lib/package.json
|
|
303
|
+
|
|
304
|
+
ā ļø unknown-lib@1.0.0 (ā UNKNOWN)
|
|
305
|
+
No license field found
|
|
306
|
+
Location: node_modules/unknown-lib/package.json
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Accessibility
|
|
310
|
+
|
|
311
|
+
Colors work in both light and dark terminal themes, and emojis are used as secondary indicators for colorblind users:
|
|
312
|
+
- Green = š¢ (green circle)
|
|
313
|
+
- Yellow = ā ļø (warning triangle)
|
|
314
|
+
- Red = ā (red X)
|
|
315
|
+
- Gray = ā (question mark)
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Update Notifications
|
|
320
|
+
|
|
321
|
+
LicenseGuard automatically checks for updates once per day (cached for 24 hours). When a new version is available, you'll see:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
325
|
+
ā Update available: 2.1.0 ā 2.1.1 ā
|
|
326
|
+
ā Run: npm install -g licenseguard-cli@latest ā
|
|
327
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Update check behavior:**
|
|
331
|
+
- Checks npm registry once per 24 hours
|
|
332
|
+
- Non-blocking (doesn't slow down CLI startup)
|
|
333
|
+
- Fails silently if network unavailable
|
|
334
|
+
- Result cached in OS temp directory
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
272
338
|
## Supported Licenses
|
|
273
339
|
|
|
274
340
|
| Key | Name | Description |
|
package/bin/licenseguard.js
CHANGED
|
@@ -6,7 +6,14 @@ const { version } = require('../package.json')
|
|
|
6
6
|
const { runList } = require('../lib/commands/list')
|
|
7
7
|
const { runInit } = require('../lib/commands/init')
|
|
8
8
|
const { runInitFast } = require('../lib/commands/init-fast')
|
|
9
|
+
const { runScan } = require('../lib/commands/scan')
|
|
9
10
|
const { setupCommand } = require('../lib/commands/setup')
|
|
11
|
+
const { checkForUpdates } = require('../lib/utils/update-notifier')
|
|
12
|
+
|
|
13
|
+
// Check for updates (non-blocking, silent failure)
|
|
14
|
+
checkForUpdates(version).catch(() => {
|
|
15
|
+
// Silent failure - don't block user
|
|
16
|
+
})
|
|
10
17
|
|
|
11
18
|
program
|
|
12
19
|
.version(version)
|
|
@@ -37,6 +44,24 @@ program
|
|
|
37
44
|
}
|
|
38
45
|
})
|
|
39
46
|
|
|
47
|
+
// Scan command
|
|
48
|
+
program
|
|
49
|
+
.command('scan')
|
|
50
|
+
.description('Scan dependencies for license conflicts')
|
|
51
|
+
.option('--license <type>', 'Specify project license (if not auto-detected)')
|
|
52
|
+
.option('--allow', 'Allow conflicts (exit 0 even if conflicts found)')
|
|
53
|
+
.option('--fail-on-unknown', 'Fail if unknown licenses detected')
|
|
54
|
+
.option('--explain', 'Show authoritative source citations for license compatibility decisions')
|
|
55
|
+
.option('--cwd <path>', 'Working directory to scan')
|
|
56
|
+
.action(async (options) => {
|
|
57
|
+
try {
|
|
58
|
+
await runScan(options)
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(chalk.red('ā Error:'), error.message)
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
40
65
|
// List command
|
|
41
66
|
program
|
|
42
67
|
.command('ls')
|
package/lib/commands/scan.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
const chalk = require('chalk')
|
|
7
7
|
const fs = require('fs')
|
|
8
|
-
const path = require('path')
|
|
9
8
|
const { scanDependencies, displayConflictReport } = require('../scanner')
|
|
10
9
|
const { detectLicenseFromText } = require('../scanner/license-detector')
|
|
11
10
|
|
|
@@ -41,7 +40,9 @@ function detectProjectLicense() {
|
|
|
41
40
|
try {
|
|
42
41
|
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
|
43
42
|
if (pkg.license) return pkg.license
|
|
44
|
-
} catch (e) {
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// Ignore malformed package.json
|
|
45
|
+
}
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
if (fs.existsSync('Cargo.toml')) {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const chalk = require('chalk')
|
|
2
|
+
|
|
3
|
+
// License color mapping based on safety level
|
|
4
|
+
const LICENSE_COLORS = {
|
|
5
|
+
// Green: Permissive licenses
|
|
6
|
+
'MIT': 'green',
|
|
7
|
+
'Apache-2.0': 'green',
|
|
8
|
+
'BSD-2-Clause': 'green',
|
|
9
|
+
'BSD-3-Clause': 'green',
|
|
10
|
+
'ISC': 'green',
|
|
11
|
+
'0BSD': 'green',
|
|
12
|
+
'Unlicense': 'green',
|
|
13
|
+
|
|
14
|
+
// Yellow: Weak copyleft
|
|
15
|
+
'MPL-2.0': 'yellow',
|
|
16
|
+
'LGPL-2.1': 'yellow',
|
|
17
|
+
'LGPL-3.0': 'yellow',
|
|
18
|
+
'EPL-1.0': 'yellow',
|
|
19
|
+
'EPL-2.0': 'yellow',
|
|
20
|
+
|
|
21
|
+
// Red: Strong copyleft
|
|
22
|
+
'GPL-2.0': 'red',
|
|
23
|
+
'GPL-3.0': 'red',
|
|
24
|
+
'AGPL-3.0': 'red',
|
|
25
|
+
|
|
26
|
+
// Gray: Unknown
|
|
27
|
+
'UNKNOWN': 'gray'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Emoji indicators for accessibility
|
|
31
|
+
const EMOJI_INDICATORS = {
|
|
32
|
+
green: 'š¢',
|
|
33
|
+
yellow: 'ā ļø',
|
|
34
|
+
red: 'ā',
|
|
35
|
+
gray: 'ā'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get color for a given license
|
|
40
|
+
* @param {string} license - License identifier (e.g., "MIT", "GPL-3.0")
|
|
41
|
+
* @returns {string} - Color name (green/yellow/red/gray)
|
|
42
|
+
*/
|
|
43
|
+
function getColor(license) {
|
|
44
|
+
return LICENSE_COLORS[license] || 'gray'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get emoji indicator for a given license
|
|
49
|
+
* @param {string} license - License identifier
|
|
50
|
+
* @returns {string} - Emoji indicator
|
|
51
|
+
*/
|
|
52
|
+
function getEmoji(license) {
|
|
53
|
+
const color = getColor(license)
|
|
54
|
+
return EMOJI_INDICATORS[color]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Colorize text based on license safety level
|
|
59
|
+
* @param {string} license - License identifier
|
|
60
|
+
* @param {string} text - Text to colorize
|
|
61
|
+
* @returns {string} - Colorized text with chalk
|
|
62
|
+
*/
|
|
63
|
+
function colorize(license, text) {
|
|
64
|
+
const color = getColor(license)
|
|
65
|
+
return chalk[color](text)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Colorize text with emoji indicator
|
|
70
|
+
* @param {string} license - License identifier
|
|
71
|
+
* @param {string} text - Text to colorize
|
|
72
|
+
* @returns {string} - Colorized text with emoji prefix
|
|
73
|
+
*/
|
|
74
|
+
function colorizeWithEmoji(license, text) {
|
|
75
|
+
const emoji = getEmoji(license)
|
|
76
|
+
const coloredText = colorize(license, text)
|
|
77
|
+
return `${emoji} ${coloredText}`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
getColor,
|
|
82
|
+
getEmoji,
|
|
83
|
+
colorize,
|
|
84
|
+
colorizeWithEmoji,
|
|
85
|
+
LICENSE_COLORS,
|
|
86
|
+
EMOJI_INDICATORS
|
|
87
|
+
}
|
package/lib/scanner/index.js
CHANGED
|
@@ -8,6 +8,9 @@ const chalk = require('chalk')
|
|
|
8
8
|
// Import explainCompatibility for --explain flag support
|
|
9
9
|
const { explainCompatibility } = require('./compat-checker')
|
|
10
10
|
|
|
11
|
+
// Import color mapper for license color-coding
|
|
12
|
+
const { colorizeWithEmoji } = require('./color-mapper')
|
|
13
|
+
|
|
11
14
|
// Import ecosystem plugins
|
|
12
15
|
const nodePlugin = require('./plugins/node')
|
|
13
16
|
const cppPlugin = require('./plugins/cpp')
|
|
@@ -83,10 +86,13 @@ function displayConflictReport(scanResult, projectLicense, options = {}) {
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
for (const issue of scanResult.issues) {
|
|
89
|
+
// Color-code the license based on safety level
|
|
90
|
+
const coloredLicense = colorizeWithEmoji(issue.license, issue.license)
|
|
91
|
+
|
|
86
92
|
if (issue.type === 'conflict') {
|
|
87
|
-
console.log(chalk.red(`ā ${issue.package} (${
|
|
93
|
+
console.log(chalk.red(`ā ${issue.package} (${coloredLicense})`))
|
|
88
94
|
} else {
|
|
89
|
-
console.log(chalk.yellow(`ā ļø ${issue.package} (${
|
|
95
|
+
console.log(chalk.yellow(`ā ļø ${issue.package} (${coloredLicense})`))
|
|
90
96
|
}
|
|
91
97
|
console.log(chalk.gray(` ${issue.reason}`))
|
|
92
98
|
console.log(chalk.gray(` Location: ${issue.location}`))
|
|
@@ -167,7 +167,7 @@ function parsePyprojectToml() {
|
|
|
167
167
|
* @param {string} manager - Package manager (for error messages)
|
|
168
168
|
* @returns {Object} Map of { packageName: licenseString }
|
|
169
169
|
*/
|
|
170
|
-
function getLicensesBatch(packageNames,
|
|
170
|
+
function getLicensesBatch(packageNames, _manager) {
|
|
171
171
|
if (packageNames.length === 0) return {}
|
|
172
172
|
|
|
173
173
|
const CHUNK_SIZE = 100 // Process in chunks to avoid stdin size limits
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const https = require('https')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const os = require('os')
|
|
5
|
+
const chalk = require('chalk')
|
|
6
|
+
|
|
7
|
+
const CACHE_FILE = path.join(os.tmpdir(), 'licenseguard-update-check')
|
|
8
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours
|
|
9
|
+
const FETCH_TIMEOUT_MS = 5000 // 5 seconds
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fetch package version info from npm registry
|
|
13
|
+
* @param {string} packageName - Package name to check
|
|
14
|
+
* @returns {Promise<Object>} - Package metadata from npm registry
|
|
15
|
+
*/
|
|
16
|
+
function fetchNpmVersion(packageName) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const options = {
|
|
19
|
+
hostname: 'registry.npmjs.org',
|
|
20
|
+
path: `/${packageName}`,
|
|
21
|
+
method: 'GET',
|
|
22
|
+
timeout: FETCH_TIMEOUT_MS
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const req = https.request(options, (res) => {
|
|
26
|
+
let data = ''
|
|
27
|
+
|
|
28
|
+
res.on('data', (chunk) => {
|
|
29
|
+
data += chunk
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
res.on('end', () => {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(data)
|
|
35
|
+
resolve(parsed)
|
|
36
|
+
} catch (error) {
|
|
37
|
+
reject(new Error('Failed to parse npm registry response'))
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
req.on('error', (error) => {
|
|
43
|
+
reject(error)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
req.on('timeout', () => {
|
|
47
|
+
req.destroy()
|
|
48
|
+
reject(new Error('npm registry request timeout'))
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
req.end()
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if cached result is still valid
|
|
57
|
+
* @returns {boolean} - True if cache exists and is fresh
|
|
58
|
+
*/
|
|
59
|
+
function isCacheValid() {
|
|
60
|
+
if (!fs.existsSync(CACHE_FILE)) {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const stat = fs.statSync(CACHE_FILE)
|
|
66
|
+
const age = Date.now() - stat.mtimeMs
|
|
67
|
+
return age < CACHE_TTL_MS
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Compare two semantic version strings
|
|
75
|
+
* @param {string} current - Current version (e.g., "2.1.0")
|
|
76
|
+
* @param {string} latest - Latest version (e.g., "2.2.0")
|
|
77
|
+
* @returns {boolean} - True if latest is newer than current
|
|
78
|
+
*/
|
|
79
|
+
function isNewerVersion(current, latest) {
|
|
80
|
+
const currentParts = current.split('.').map(Number)
|
|
81
|
+
const latestParts = latest.split('.').map(Number)
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < 3; i++) {
|
|
84
|
+
if (latestParts[i] > currentParts[i]) return true
|
|
85
|
+
if (latestParts[i] < currentParts[i]) return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false // Versions are equal
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Display update banner in terminal
|
|
93
|
+
* @param {string} current - Current version
|
|
94
|
+
* @param {string} latest - Latest available version
|
|
95
|
+
*/
|
|
96
|
+
function displayUpdateBanner(current, latest) {
|
|
97
|
+
console.log(chalk.yellow('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'))
|
|
98
|
+
console.log(chalk.yellow(`ā Update available: ${current} ā ${latest} ā`))
|
|
99
|
+
console.log(chalk.yellow('ā Run: npm install -g licenseguard-cli@latest ā'))
|
|
100
|
+
console.log(chalk.yellow('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check for updates from npm registry (with caching)
|
|
105
|
+
* Non-blocking, silent failure on errors
|
|
106
|
+
* @param {string} currentVersion - Current version from package.json
|
|
107
|
+
* @returns {Promise<void>}
|
|
108
|
+
*/
|
|
109
|
+
async function checkForUpdates(currentVersion) {
|
|
110
|
+
try {
|
|
111
|
+
// Check cache validity
|
|
112
|
+
if (isCacheValid()) {
|
|
113
|
+
return // Skip check - cache is fresh
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Fetch latest version from npm registry
|
|
117
|
+
const data = await fetchNpmVersion('licenseguard-cli')
|
|
118
|
+
const latestVersion = data['dist-tags'].latest
|
|
119
|
+
|
|
120
|
+
// Cache result
|
|
121
|
+
fs.writeFileSync(CACHE_FILE, latestVersion, 'utf8')
|
|
122
|
+
|
|
123
|
+
// Compare versions and display banner if update available
|
|
124
|
+
if (isNewerVersion(currentVersion, latestVersion)) {
|
|
125
|
+
displayUpdateBanner(currentVersion, latestVersion)
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// Silent failure - don't block user or pollute output
|
|
129
|
+
// Network issues, timeouts, parsing errors all handled gracefully
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
checkForUpdates,
|
|
135
|
+
displayUpdateBanner,
|
|
136
|
+
isNewerVersion,
|
|
137
|
+
fetchNpmVersion,
|
|
138
|
+
isCacheValid,
|
|
139
|
+
CACHE_FILE,
|
|
140
|
+
CACHE_TTL_MS
|
|
141
|
+
}
|