licenseguard-cli 2.0.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 +111 -9
- package/bin/licenseguard.js +26 -0
- package/lib/commands/init-fast.js +2 -10
- package/lib/commands/init.js +3 -11
- package/lib/commands/scan.js +122 -0
- package/lib/scanner/color-mapper.js +87 -0
- package/lib/scanner/compat-checker.js +369 -50
- package/lib/scanner/index.js +75 -117
- package/lib/scanner/license-compatibility-matrix.json +338 -0
- package/lib/scanner/license-detector.js +847 -0
- package/lib/scanner/license-normalizer.js +357 -0
- package/lib/scanner/plugins/cpp.js +267 -0
- package/lib/scanner/plugins/go.js +420 -0
- package/lib/scanner/plugins/node.js +149 -0
- package/lib/scanner/plugins/python-license-scanner.py +173 -0
- package/lib/scanner/plugins/python.js +336 -0
- package/lib/scanner/plugins/rust.js +196 -0
- package/lib/utils/license-mapper.js +28 -0
- package/lib/utils/update-notifier.js +141 -0
- package/package.json +2 -2
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
|
@@ -9,7 +9,7 @@ LicenseGuard helps you set up open source licenses and protects your project fro
|
|
|
9
9
|
|
|
10
10
|
## Key Features
|
|
11
11
|
|
|
12
|
-
- **
|
|
12
|
+
- **Multi-Ecosystem Scanning** - Scans dependencies across 5 ecosystems (Node.js, C++, Rust, Python, Go)
|
|
13
13
|
- **Conflict Detection** - Detects incompatible licenses (e.g., GPL vs MIT) and blocks creation
|
|
14
14
|
- **SPDX Compatibility** - Industry-standard license compatibility checking
|
|
15
15
|
- **Scan Results** - Save scan results to `.licenseguardrc` for transparency
|
|
@@ -20,6 +20,20 @@ LicenseGuard helps you set up open source licenses and protects your project fro
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
## Supported Ecosystems
|
|
24
|
+
|
|
25
|
+
LicenseGuard scans dependencies across multiple languages and package managers:
|
|
26
|
+
|
|
27
|
+
- **Node.js** - npm packages (`package.json`)
|
|
28
|
+
- **C/C++** - Conan packages (`conanfile.txt`, `conanfile.py`)
|
|
29
|
+
- **Rust** - Cargo crates (`Cargo.toml`)
|
|
30
|
+
- **Python** - pip/pipenv/poetry packages (`requirements.txt`, `Pipfile`, `pyproject.toml`)
|
|
31
|
+
- **Go** - Go modules (`go.mod`)
|
|
32
|
+
|
|
33
|
+
Each ecosystem uses optimized detection strategies for maximum accuracy.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
23
37
|
## Quick Start
|
|
24
38
|
|
|
25
39
|
### For Developers (One-time Setup)
|
|
@@ -175,9 +189,21 @@ Fix conflicts or use --force to proceed anyway:
|
|
|
175
189
|
licenseguard init --force
|
|
176
190
|
```
|
|
177
191
|
|
|
192
|
+
**With Explanation (`--explain`):**
|
|
193
|
+
```bash
|
|
194
|
+
licenseguard init --explain
|
|
195
|
+
# ...
|
|
196
|
+
# ā libdwarf@0.9.1 (LGPL-2.1-only)
|
|
197
|
+
# Conflict: Copyleft incompatible with MIT
|
|
198
|
+
# āāāāāāāāāāāāāāāāāāāāāāāā
|
|
199
|
+
# š FSF: MIT license is permissive and GPL-compatible
|
|
200
|
+
# š https://www.gnu.org/licenses/license-list.html#Expat
|
|
201
|
+
```
|
|
202
|
+
|
|
178
203
|
**Flags:**
|
|
179
204
|
- `--force` - Create LICENSE despite conflicts (shows warnings)
|
|
180
205
|
- `--noscan` - Skip dependency scanning
|
|
206
|
+
- `--explain` - Show authoritative source citations (FSF/OSI links) for conflicts
|
|
181
207
|
|
|
182
208
|
### `init --fast` - Non-Interactive Setup
|
|
183
209
|
|
|
@@ -243,6 +269,72 @@ Reads existing `.licenseguardrc` and installs hooks. Used in npm prepare scripts
|
|
|
243
269
|
|
|
244
270
|
---
|
|
245
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
|
+
|
|
246
338
|
## Supported Licenses
|
|
247
339
|
|
|
248
340
|
| Key | Name | Description |
|
|
@@ -339,12 +431,20 @@ LicenseGuard works without git:
|
|
|
339
431
|
|
|
340
432
|
### What licenses are checked during scanning?
|
|
341
433
|
|
|
342
|
-
|
|
434
|
+
LicenseGuard **auto-detects your project type** and scans the appropriate ecosystem:
|
|
435
|
+
- **Node.js**: Reads `package.json` and `node_modules/*/package.json`
|
|
436
|
+
- **C/C++**: Reads Conan metadata via `conan graph info`
|
|
437
|
+
- **Rust**: Reads `cargo metadata` JSON output
|
|
438
|
+
- **Python**: Uses native Python `importlib.metadata` (98.6% detection rate)
|
|
439
|
+
- **Go**: Reads `go.mod` and scans `GOMODCACHE` with streaming NDJSON
|
|
440
|
+
|
|
441
|
+
All ecosystems check:
|
|
343
442
|
- SPDX license identifiers (MIT, Apache-2.0, GPL-3.0, BSD-3-Clause, ISC, etc.)
|
|
344
443
|
- License compatibility using industry-standard rules
|
|
345
444
|
- Copyleft vs permissive conflicts (e.g., GPL incompatible with MIT)
|
|
445
|
+
- Multi-strategy detection including Jaccard Index similarity matching
|
|
346
446
|
|
|
347
|
-
|
|
447
|
+
Mixed-language projects are not yet supported. Use `--noscan` flag if detection is incorrect.
|
|
348
448
|
|
|
349
449
|
### How does SPDX compatibility work?
|
|
350
450
|
|
|
@@ -378,12 +478,14 @@ This is useful for:
|
|
|
378
478
|
|
|
379
479
|
### Does this work for non-JavaScript projects?
|
|
380
480
|
|
|
381
|
-
**Yes!** LicenseGuard
|
|
382
|
-
-
|
|
383
|
-
-
|
|
384
|
-
-
|
|
385
|
-
-
|
|
386
|
-
-
|
|
481
|
+
**Yes!** LicenseGuard natively supports 5 ecosystems:
|
|
482
|
+
- **Node.js** - Full dependency scanning
|
|
483
|
+
- **C/C++** - Conan package scanning (requires Conan 2.x or 1.x installed)
|
|
484
|
+
- **Rust** - Cargo crate scanning (requires Cargo installed)
|
|
485
|
+
- **Python** - Native package scanning with 98.6% accuracy (requires Python 3.7+)
|
|
486
|
+
- **Go** - Go module scanning (requires Go installed)
|
|
487
|
+
|
|
488
|
+
For other languages (Ruby, PHP, etc.), the LICENSE file and git hooks still work, but dependency scanning is not yet available. Use `--noscan` flag for those projects.
|
|
387
489
|
|
|
388
490
|
The hooks only need Node.js installed (which most developers have).
|
|
389
491
|
|
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)
|
|
@@ -18,6 +25,7 @@ program
|
|
|
18
25
|
.description('Interactive license setup with dependency scanning')
|
|
19
26
|
.option('--force', 'Create LICENSE despite conflicts')
|
|
20
27
|
.option('--noscan', 'Skip dependency scanning')
|
|
28
|
+
.option('--explain', 'Show authoritative source citations for license compatibility decisions')
|
|
21
29
|
.option('--fast', 'Non-interactive mode with auto-detection')
|
|
22
30
|
.option('--license <type>', 'License type (for --fast mode)')
|
|
23
31
|
.option('--owner <name>', 'Copyright owner name (for --fast mode)')
|
|
@@ -36,6 +44,24 @@ program
|
|
|
36
44
|
}
|
|
37
45
|
})
|
|
38
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
|
+
|
|
39
65
|
// List command
|
|
40
66
|
program
|
|
41
67
|
.command('ls')
|
|
@@ -6,6 +6,7 @@ const { generateLicense, LICENSE_TEMPLATES } = require('../templates')
|
|
|
6
6
|
const { writeConfig } = require('../utils/file-ops')
|
|
7
7
|
const { isGitRepo, installHooks } = require('../utils/git-helpers')
|
|
8
8
|
const { scanDependencies, displayConflictReport } = require('../scanner')
|
|
9
|
+
const { toSPDX } = require('../utils/license-mapper')
|
|
9
10
|
|
|
10
11
|
function getGitConfig(key) {
|
|
11
12
|
try {
|
|
@@ -67,16 +68,7 @@ async function runInitFast(options) {
|
|
|
67
68
|
// Scanner integration (Story 2.3) - Fast mode
|
|
68
69
|
let scanResult = null
|
|
69
70
|
if (!options.noscan) {
|
|
70
|
-
|
|
71
|
-
const licenseToSPDX = {
|
|
72
|
-
'mit': 'MIT',
|
|
73
|
-
'apache2_0': 'Apache-2.0',
|
|
74
|
-
'gpl3_0': 'GPL-3.0',
|
|
75
|
-
'bsd3clause': 'BSD-3-Clause',
|
|
76
|
-
'isc': 'ISC',
|
|
77
|
-
'wtfpl': 'WTFPL'
|
|
78
|
-
}
|
|
79
|
-
const spdxLicense = licenseToSPDX[flags.license] || flags.license.toUpperCase()
|
|
71
|
+
const spdxLicense = toSPDX(flags.license)
|
|
80
72
|
|
|
81
73
|
try {
|
|
82
74
|
console.log(chalk.blue('š Scanning dependencies for license conflicts...\n'))
|
package/lib/commands/init.js
CHANGED
|
@@ -4,6 +4,7 @@ const { generateLicense } = require('../templates')
|
|
|
4
4
|
const { writeLicenseFile, writeConfig } = require('../utils/file-ops')
|
|
5
5
|
const { isGitRepo, initGitRepo, installHooks } = require('../utils/git-helpers')
|
|
6
6
|
const { scanDependencies, displayConflictReport } = require('../scanner')
|
|
7
|
+
const { toSPDX } = require('../utils/license-mapper')
|
|
7
8
|
|
|
8
9
|
async function runInit(options = {}) {
|
|
9
10
|
try {
|
|
@@ -52,22 +53,13 @@ async function runInit(options = {}) {
|
|
|
52
53
|
// Scanner integration (Story 2.3)
|
|
53
54
|
let scanResult = null
|
|
54
55
|
if (!options.noscan) {
|
|
55
|
-
|
|
56
|
-
const licenseToSPDX = {
|
|
57
|
-
'mit': 'MIT',
|
|
58
|
-
'apache2_0': 'Apache-2.0',
|
|
59
|
-
'gpl3_0': 'GPL-3.0',
|
|
60
|
-
'bsd3clause': 'BSD-3-Clause',
|
|
61
|
-
'isc': 'ISC',
|
|
62
|
-
'wtfpl': 'WTFPL'
|
|
63
|
-
}
|
|
64
|
-
const spdxLicense = licenseToSPDX[answers.license] || answers.license.toUpperCase()
|
|
56
|
+
const spdxLicense = toSPDX(answers.license)
|
|
65
57
|
|
|
66
58
|
try {
|
|
67
59
|
console.log(chalk.blue('\nš Scanning dependencies for license conflicts...\n'))
|
|
68
60
|
|
|
69
61
|
scanResult = await scanDependencies(spdxLicense)
|
|
70
|
-
const hasConflicts = displayConflictReport(scanResult, spdxLicense)
|
|
62
|
+
const hasConflicts = displayConflictReport(scanResult, spdxLicense, { explain: options.explain })
|
|
71
63
|
|
|
72
64
|
if (hasConflicts && !options.force) {
|
|
73
65
|
// Block LICENSE creation due to conflicts
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scan Command Handler
|
|
3
|
+
* Runs dependency scanning without modifying project files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk')
|
|
7
|
+
const fs = require('fs')
|
|
8
|
+
const { scanDependencies, displayConflictReport } = require('../scanner')
|
|
9
|
+
const { detectLicenseFromText } = require('../scanner/license-detector')
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Try to detect project license from local files
|
|
13
|
+
* @returns {string|null} Detected license or null
|
|
14
|
+
*/
|
|
15
|
+
function detectProjectLicense() {
|
|
16
|
+
// 1. Try .licenseguardrc
|
|
17
|
+
if (fs.existsSync('.licenseguardrc')) {
|
|
18
|
+
try {
|
|
19
|
+
const config = JSON.parse(fs.readFileSync('.licenseguardrc', 'utf8'))
|
|
20
|
+
if (config.license) return config.license
|
|
21
|
+
} catch (e) {
|
|
22
|
+
// Ignore config error
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 2. Try LICENSE file
|
|
27
|
+
const licenseFiles = ['LICENSE', 'LICENSE.txt', 'LICENSE.md', 'COPYING']
|
|
28
|
+
for (const file of licenseFiles) {
|
|
29
|
+
if (fs.existsSync(file)) {
|
|
30
|
+
const content = fs.readFileSync(file, 'utf8')
|
|
31
|
+
const detected = detectLicenseFromText(content)
|
|
32
|
+
if (detected && detected !== 'UNKNOWN') {
|
|
33
|
+
return detected
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 3. Try package managers (basic check)
|
|
39
|
+
if (fs.existsSync('package.json')) {
|
|
40
|
+
try {
|
|
41
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
|
42
|
+
if (pkg.license) return pkg.license
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// Ignore malformed package.json
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (fs.existsSync('Cargo.toml')) {
|
|
49
|
+
// TODO: Parse Cargo.toml for license
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run the scan command
|
|
57
|
+
* @param {Object} options - Command options
|
|
58
|
+
*/
|
|
59
|
+
async function runScan(options) {
|
|
60
|
+
// 1. Handle CWD
|
|
61
|
+
if (options.cwd) {
|
|
62
|
+
try {
|
|
63
|
+
process.chdir(options.cwd)
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(`Failed to change directory to ${options.cwd}: ${error.message}`)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(chalk.blue(`š Scanning in: ${process.cwd()}`))
|
|
70
|
+
|
|
71
|
+
// 2. Determine Project License
|
|
72
|
+
let projectLicense = options.license
|
|
73
|
+
|
|
74
|
+
if (!projectLicense) {
|
|
75
|
+
projectLicense = detectProjectLicense()
|
|
76
|
+
if (projectLicense) {
|
|
77
|
+
console.log(chalk.blue(`ā¹ļø Detected project license: ${projectLicense}`))
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!projectLicense) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
'Could not determine project license.\n' +
|
|
84
|
+
'Please create a LICENSE file, use "licenseguard init", or specify --license <type>'
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 3. Run Scan
|
|
89
|
+
console.log(chalk.gray('š Scanning dependencies...'))
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const results = await scanDependencies(projectLicense)
|
|
93
|
+
|
|
94
|
+
// 4. Display Report
|
|
95
|
+
const hasConflicts = displayConflictReport(results, projectLicense, {
|
|
96
|
+
explain: options.explain
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// 5. Handle Exit Code
|
|
100
|
+
if (hasConflicts && !options.allow) {
|
|
101
|
+
console.log(chalk.red('\nā Scan failed: License conflicts detected.'))
|
|
102
|
+
process.exit(1)
|
|
103
|
+
} else if (hasConflicts && options.allow) {
|
|
104
|
+
console.log(chalk.yellow('\nā ļø Scan passed (conflicts allowed via flag).'))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Handle unknown licenses if fail-on-unknown flag is set
|
|
108
|
+
if (options.failOnUnknown && results.unknown > 0) {
|
|
109
|
+
console.log(chalk.red('\nā Scan failed: Unknown licenses detected (--fail-on-unknown).'))
|
|
110
|
+
process.exit(1)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// Check for specific plugin errors
|
|
115
|
+
if (error.message.includes('No supported package manager')) {
|
|
116
|
+
throw new Error('No supported package manager found (package.json, go.mod, Cargo.toml, etc.)')
|
|
117
|
+
}
|
|
118
|
+
throw error
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { runScan }
|
|
@@ -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
|
+
}
|