licenseguard-cli 2.0.0 → 2.1.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/README.md +45 -9
- package/bin/licenseguard.js +1 -0
- package/lib/commands/init-fast.js +2 -10
- package/lib/commands/init.js +3 -11
- package/lib/commands/scan.js +121 -0
- package/lib/scanner/compat-checker.js +369 -50
- package/lib/scanner/index.js +67 -115
- 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 +421 -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/package.json +2 -2
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
|
|
|
@@ -339,12 +365,20 @@ LicenseGuard works without git:
|
|
|
339
365
|
|
|
340
366
|
### What licenses are checked during scanning?
|
|
341
367
|
|
|
342
|
-
|
|
368
|
+
LicenseGuard **auto-detects your project type** and scans the appropriate ecosystem:
|
|
369
|
+
- **Node.js**: Reads `package.json` and `node_modules/*/package.json`
|
|
370
|
+
- **C/C++**: Reads Conan metadata via `conan graph info`
|
|
371
|
+
- **Rust**: Reads `cargo metadata` JSON output
|
|
372
|
+
- **Python**: Uses native Python `importlib.metadata` (98.6% detection rate)
|
|
373
|
+
- **Go**: Reads `go.mod` and scans `GOMODCACHE` with streaming NDJSON
|
|
374
|
+
|
|
375
|
+
All ecosystems check:
|
|
343
376
|
- SPDX license identifiers (MIT, Apache-2.0, GPL-3.0, BSD-3-Clause, ISC, etc.)
|
|
344
377
|
- License compatibility using industry-standard rules
|
|
345
378
|
- Copyleft vs permissive conflicts (e.g., GPL incompatible with MIT)
|
|
379
|
+
- Multi-strategy detection including Jaccard Index similarity matching
|
|
346
380
|
|
|
347
|
-
|
|
381
|
+
Mixed-language projects are not yet supported. Use `--noscan` flag if detection is incorrect.
|
|
348
382
|
|
|
349
383
|
### How does SPDX compatibility work?
|
|
350
384
|
|
|
@@ -378,12 +412,14 @@ This is useful for:
|
|
|
378
412
|
|
|
379
413
|
### Does this work for non-JavaScript projects?
|
|
380
414
|
|
|
381
|
-
**Yes!** LicenseGuard
|
|
382
|
-
-
|
|
383
|
-
-
|
|
384
|
-
-
|
|
385
|
-
-
|
|
386
|
-
-
|
|
415
|
+
**Yes!** LicenseGuard natively supports 5 ecosystems:
|
|
416
|
+
- **Node.js** - Full dependency scanning
|
|
417
|
+
- **C/C++** - Conan package scanning (requires Conan 2.x or 1.x installed)
|
|
418
|
+
- **Rust** - Cargo crate scanning (requires Cargo installed)
|
|
419
|
+
- **Python** - Native package scanning with 98.6% accuracy (requires Python 3.7+)
|
|
420
|
+
- **Go** - Go module scanning (requires Go installed)
|
|
421
|
+
|
|
422
|
+
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
423
|
|
|
388
424
|
The hooks only need Node.js installed (which most developers have).
|
|
389
425
|
|
package/bin/licenseguard.js
CHANGED
|
@@ -18,6 +18,7 @@ program
|
|
|
18
18
|
.description('Interactive license setup with dependency scanning')
|
|
19
19
|
.option('--force', 'Create LICENSE despite conflicts')
|
|
20
20
|
.option('--noscan', 'Skip dependency scanning')
|
|
21
|
+
.option('--explain', 'Show authoritative source citations for license compatibility decisions')
|
|
21
22
|
.option('--fast', 'Non-interactive mode with auto-detection')
|
|
22
23
|
.option('--license <type>', 'License type (for --fast mode)')
|
|
23
24
|
.option('--owner <name>', 'Copyright owner name (for --fast mode)')
|
|
@@ -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,121 @@
|
|
|
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 path = require('path')
|
|
9
|
+
const { scanDependencies, displayConflictReport } = require('../scanner')
|
|
10
|
+
const { detectLicenseFromText } = require('../scanner/license-detector')
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Try to detect project license from local files
|
|
14
|
+
* @returns {string|null} Detected license or null
|
|
15
|
+
*/
|
|
16
|
+
function detectProjectLicense() {
|
|
17
|
+
// 1. Try .licenseguardrc
|
|
18
|
+
if (fs.existsSync('.licenseguardrc')) {
|
|
19
|
+
try {
|
|
20
|
+
const config = JSON.parse(fs.readFileSync('.licenseguardrc', 'utf8'))
|
|
21
|
+
if (config.license) return config.license
|
|
22
|
+
} catch (e) {
|
|
23
|
+
// Ignore config error
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 2. Try LICENSE file
|
|
28
|
+
const licenseFiles = ['LICENSE', 'LICENSE.txt', 'LICENSE.md', 'COPYING']
|
|
29
|
+
for (const file of licenseFiles) {
|
|
30
|
+
if (fs.existsSync(file)) {
|
|
31
|
+
const content = fs.readFileSync(file, 'utf8')
|
|
32
|
+
const detected = detectLicenseFromText(content)
|
|
33
|
+
if (detected && detected !== 'UNKNOWN') {
|
|
34
|
+
return detected
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3. Try package managers (basic check)
|
|
40
|
+
if (fs.existsSync('package.json')) {
|
|
41
|
+
try {
|
|
42
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
|
43
|
+
if (pkg.license) return pkg.license
|
|
44
|
+
} catch (e) {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (fs.existsSync('Cargo.toml')) {
|
|
48
|
+
// TODO: Parse Cargo.toml for license
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Run the scan command
|
|
56
|
+
* @param {Object} options - Command options
|
|
57
|
+
*/
|
|
58
|
+
async function runScan(options) {
|
|
59
|
+
// 1. Handle CWD
|
|
60
|
+
if (options.cwd) {
|
|
61
|
+
try {
|
|
62
|
+
process.chdir(options.cwd)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw new Error(`Failed to change directory to ${options.cwd}: ${error.message}`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(chalk.blue(`📂 Scanning in: ${process.cwd()}`))
|
|
69
|
+
|
|
70
|
+
// 2. Determine Project License
|
|
71
|
+
let projectLicense = options.license
|
|
72
|
+
|
|
73
|
+
if (!projectLicense) {
|
|
74
|
+
projectLicense = detectProjectLicense()
|
|
75
|
+
if (projectLicense) {
|
|
76
|
+
console.log(chalk.blue(`ℹ️ Detected project license: ${projectLicense}`))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!projectLicense) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
'Could not determine project license.\n' +
|
|
83
|
+
'Please create a LICENSE file, use "licenseguard init", or specify --license <type>'
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 3. Run Scan
|
|
88
|
+
console.log(chalk.gray('🔍 Scanning dependencies...'))
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const results = await scanDependencies(projectLicense)
|
|
92
|
+
|
|
93
|
+
// 4. Display Report
|
|
94
|
+
const hasConflicts = displayConflictReport(results, projectLicense, {
|
|
95
|
+
explain: options.explain
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// 5. Handle Exit Code
|
|
99
|
+
if (hasConflicts && !options.allow) {
|
|
100
|
+
console.log(chalk.red('\n❌ Scan failed: License conflicts detected.'))
|
|
101
|
+
process.exit(1)
|
|
102
|
+
} else if (hasConflicts && options.allow) {
|
|
103
|
+
console.log(chalk.yellow('\n⚠️ Scan passed (conflicts allowed via flag).'))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Handle unknown licenses if fail-on-unknown flag is set
|
|
107
|
+
if (options.failOnUnknown && results.unknown > 0) {
|
|
108
|
+
console.log(chalk.red('\n❌ Scan failed: Unknown licenses detected (--fail-on-unknown).'))
|
|
109
|
+
process.exit(1)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
// Check for specific plugin errors
|
|
114
|
+
if (error.message.includes('No supported package manager')) {
|
|
115
|
+
throw new Error('No supported package manager found (package.json, go.mod, Cargo.toml, etc.)')
|
|
116
|
+
}
|
|
117
|
+
throw error
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = { runScan }
|