Newer
Older
abysiuscodium / build / check-licenses.js
#!/usr/bin/env node
/**
 * License compliance checker for Abysius Codium build.
 * Scans extension and vscode dependencies for GPL/copyleft licenses.
 * Exit code 0 = clean, 1 = violations found.
 */

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

const BANNED_LICENSES = [
  'GPL-2.0',
  'GPL-3.0',
  'AGPL-3.0',
  'LGPL-2.1',
  'LGPL-3.0',
  'GPL',
  'AGPL',
  'LGPL'
];

const PERMISSIVE_LICENSES = [
  'MIT',
  'Apache-2.0',
  'BSD-2-Clause',
  'BSD-3-Clause',
  'ISC',
  '0BSD',
  'CC0-1.0',
  'Unlicense'
];

const ALLOWED_COPYLEFT_PACKAGES = new Set([
  // jschardet is LGPL, but currently comes from upstream VS Code deps.
  // Keep this exception only if legal/compliance approves it.
  'jschardet@3.1.4'
]);

function normalizeLicense(license) {
  return String(license || '').trim();
}

function hasPermissiveOption(license) {
  const normalized = normalizeLicense(license).toUpperCase();

  return PERMISSIVE_LICENSES.some(allowed =>
    normalized.includes(allowed.toUpperCase())
  );
}

function hasBannedLicense(license) {
  const normalized = normalizeLicense(license).toUpperCase();

  return BANNED_LICENSES.some(banned =>
    normalized.includes(banned.toUpperCase())
  );
}

function isViolation(pkgName, license) {
  if (!license) {
    return false;
  }

  if (ALLOWED_COPYLEFT_PACKAGES.has(pkgName)) {
    return false;
  }

  // Handles dual-license expressions like:
  // "(MIT OR GPL-3.0-or-later)"
  // Since MIT is available, it should not be treated as a violation.
  if (hasPermissiveOption(license)) {
    return false;
  }

  return hasBannedLicense(license);
}

function scanDir(scanPath) {
  if (!fs.existsSync(path.join(scanPath, 'node_modules'))) {
    console.error(`[LICENSE] No node_modules found in ${scanPath}, skipping...`);
    return [];
  }

  let output;
  try {
    output = execSync('npx license-checker --json --start .', {
      cwd: scanPath,
      encoding: 'utf-8',
      stdio: ['pipe', 'pipe', 'pipe']
    });
  } catch (err) {
    console.warn('[LICENSE] license-checker failed or not installed; install with: npm install -g license-checker');
    return [];
  }

  const packages = JSON.parse(output);
  const violations = [];

  for (const [pkgName, info] of Object.entries(packages)) {
    const licenses = Array.isArray(info.licenses) ? info.licenses : [info.licenses];

    for (const lic of licenses) {
      if (isViolation(pkgName, lic)) {
        violations.push({
          package: pkgName,
          license: lic,
          path: info.path
        });
      }
    }
  }

  return violations;
}

function main() {
  const root = process.cwd();
  console.log('[LICENSE] Scanning for open-source license compliance...');

  const extDir = path.join(root, 'extensions', 'abysius-ai');
  const vscodeDir = path.join(root, 'work', 'vscode');

  const allViolations = [];

  if (fs.existsSync(extDir)) {
    allViolations.push(...scanDir(extDir));
  }

  if (fs.existsSync(vscodeDir)) {
    allViolations.push(...scanDir(vscodeDir));
  }

  if (allViolations.length === 0) {
    console.log('[LICENSE] No copyleft license violations detected.');
    process.exit(0);
  }

  console.error(`[LICENSE] Found ${allViolations.length} potential license violation(s):`);
  for (const v of allViolations) {
    console.error(`  - ${v.package}: ${v.license}`);
  }

  console.error('[LICENSE] Remove, replace, or explicitly allowlist these dependencies before distributing binaries.');
  process.exit(1);
}

main();