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 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
 
@@ -339,12 +365,20 @@ LicenseGuard works without git:
339
365
 
340
366
  ### What licenses are checked during scanning?
341
367
 
342
- Scans **npm dependencies only** (reads `package.json` and `node_modules`). It checks:
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
- For non-JavaScript projects, use `--noscan` flag.
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 works for any project:
382
- - Python projects
383
- - Rust/Cargo projects
384
- - Go modules
385
- - Ruby gems
386
- - Any language
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
 
@@ -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
- // 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,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 }