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 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
- - **Dependency Scanning** - Scans npm dependencies for license conflicts during setup
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
- Scans **npm dependencies only** (reads `package.json` and `node_modules`). It checks:
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
- For non-JavaScript projects, use `--noscan` flag.
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 works for any project:
382
- - Python projects
383
- - Rust/Cargo projects
384
- - Go modules
385
- - Ruby gems
386
- - Any language
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
 
@@ -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
- // Convert internal license format to SPDX identifier
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'))
@@ -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
- // Convert internal license format to SPDX identifier
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
+ }