DEV Community

Wilson Xu
Wilson Xu

Posted on

Stop Copying LICENSE Files from GitHub — Build a Generator CLI Instead

Every open-source project needs a LICENSE file. Yet most developers either forget to add one, copy-paste from another repo, or click through GitHub's template picker without thinking twice. The problem? A project without a license isn't "free to use" — it's actually all rights reserved by default.

Let's fix that by building a CLI tool that generates LICENSE files automatically using GitHub's public Licenses API.

Why Every Project Needs a LICENSE

Here's something that surprises many developers: publishing code on GitHub does not make it open source. Without an explicit license, copyright law applies by default. That means:

  • No one can legally copy your code. Even if your repo is public, others cannot fork it, modify it, or include it in their projects without your permission.
  • No one can distribute your code. Companies that audit their dependencies will flag unlicensed packages and remove them.
  • Contributors have no protection. Without a license, there's no contributor agreement. If someone submits a PR to your unlicensed repo, the legal ownership of that contribution is ambiguous.
  • Your project can't be used in production. Any organization with a legal team will reject dependencies that lack a license. This means your brilliant library might never get adopted.

The MIT License is just 21 lines. The Apache 2.0 License covers patent grants. The GPL ensures derivative works stay open. Each serves a different purpose, but any license is better than no license.

GitHub even warns you about this. When you create a repo without a license, the "About" sidebar conspicuously omits the license badge. That missing badge is a red flag for anyone evaluating your project.

GitHub's Licenses API

Most developers don't know this, but GitHub provides a free, public API for license templates. No authentication required.

List all available licenses

curl https://api.github.com/licenses
Enter fullscreen mode Exit fullscreen mode

This returns a JSON array with license keys, names, and URLs:

[
  {
    "key": "mit",
    "name": "MIT License",
    "spdx_id": "MIT",
    "url": "https://api.github.com/licenses/mit"
  },
  {
    "key": "apache-2.0",
    "name": "Apache License 2.0",
    "spdx_id": "Apache-2.0",
    "url": "https://api.github.com/licenses/apache-2.0"
  }
]
Enter fullscreen mode Exit fullscreen mode

Get a specific license template

curl https://api.github.com/licenses/mit
Enter fullscreen mode Exit fullscreen mode

The response includes a body field with the full license text, complete with placeholder tokens like [year] and [fullname] that you can replace programmatically.

{
  "key": "mit",
  "name": "MIT License",
  "body": "MIT License\n\nCopyright (c) [year] [fullname]\n\nPermission is hereby granted..."
}
Enter fullscreen mode Exit fullscreen mode

This is the foundation of our CLI. Instead of maintaining a local library of license templates, we can fetch them on-demand from GitHub and fill in the blanks.

Building the CLI

Let's build licensegen — a Node.js CLI that fetches license templates, populates them with your details, and writes a LICENSE file to your project root.

Project setup

mkdir licensegen && cd licensegen
npm init -y
Enter fullscreen mode Exit fullscreen mode

The core logic

Create index.js:

#!/usr/bin/env node

const https = require('https');
const fs = require('fs');
const path = require('path');
const readline = require('readline');

const API_BASE = 'https://api.github.com/licenses';

function fetch(url) {
  return new Promise((resolve, reject) => {
    https.get(url, { headers: { 'User-Agent': 'licensegen-cli' } }, (res) => {
      let data = '';
      res.on('data', (chunk) => (data += chunk));
      res.on('end', () => resolve(JSON.parse(data)));
      res.on('error', reject);
    });
  });
}

async function listLicenses() {
  const licenses = await fetch(API_BASE);
  console.log('\nAvailable licenses:\n');
  licenses.forEach((l) => {
    console.log(`  ${l.key.padEnd(20)} ${l.name}`);
  });
  console.log('');
}

async function generateLicense(key, fullname, year) {
  const license = await fetch(`${API_BASE}/${key}`);

  if (license.message === 'Not Found') {
    console.error(`License "${key}" not found. Run "licensegen --list" to see options.`);
    process.exit(1);
  }

  let body = license.body;
  body = body.replace(/\[year\]/g, year || new Date().getFullYear().toString());
  body = body.replace(/\[fullname\]/g, fullname);

  const outputPath = path.join(process.cwd(), 'LICENSE');
  fs.writeFileSync(outputPath, body);
  console.log(`\n${license.name} written to ${outputPath}`);
}
Enter fullscreen mode Exit fullscreen mode

Parsing arguments

Add argument handling at the bottom of index.js:

const args = process.argv.slice(2);

if (args.includes('--list') || args.includes('-l')) {
  listLicenses();
} else if (args.includes('--help') || args.includes('-h')) {
  console.log(`
Usage: licensegen [options]

Options:
  -l, --list           List available licenses
  -k, --key <key>      License key (e.g., mit, apache-2.0)
  -n, --name <name>    Copyright holder name
  -y, --year <year>    Copyright year (defaults to current year)
  -a, --auto           Auto-detect from package.json
  -h, --help           Show help
  `);
} else {
  const keyIdx = args.indexOf('--key') !== -1 ? args.indexOf('--key') : args.indexOf('-k');
  const nameIdx = args.indexOf('--name') !== -1 ? args.indexOf('--name') : args.indexOf('-n');
  const yearIdx = args.indexOf('--year') !== -1 ? args.indexOf('--year') : args.indexOf('-y');

  const key = keyIdx !== -1 ? args[keyIdx + 1] : 'mit';
  const name = nameIdx !== -1 ? args[nameIdx + 1] : null;
  const year = yearIdx !== -1 ? args[yearIdx + 1] : null;

  if (!name && !args.includes('--auto') && !args.includes('-a')) {
    console.error('Please provide --name "Your Name" or use --auto to read from package.json');
    process.exit(1);
  }

  if (args.includes('--auto') || args.includes('-a')) {
    autoGenerate(key, year);
  } else {
    generateLicense(key, name, year);
  }
}
Enter fullscreen mode Exit fullscreen mode

Auto-Detecting from package.json

This is where the CLI gets smart. Most Node.js projects already declare a license type and author in package.json. Why make the user type it again?

async function autoGenerate(keyOverride, yearOverride) {
  const pkgPath = path.join(process.cwd(), 'package.json');

  if (!fs.existsSync(pkgPath)) {
    console.error('No package.json found. Use --key and --name instead.');
    process.exit(1);
  }

  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));

  // Extract license key from package.json
  const key = keyOverride || (pkg.license ? pkg.license.toLowerCase() : 'mit');

  // Extract author name — handle both string and object formats
  let authorName = 'Unknown';
  if (typeof pkg.author === 'string') {
    // "John Doe <john@example.com>" → "John Doe"
    authorName = pkg.author.replace(/<.*>/, '').trim();
  } else if (pkg.author && pkg.author.name) {
    authorName = pkg.author.name;
  }

  const year = yearOverride || new Date().getFullYear().toString();

  console.log(`Detected: license=${key}, author=${authorName}, year=${year}`);
  await generateLicense(key, authorName, year);
}
Enter fullscreen mode Exit fullscreen mode

Now running licensegen --auto inside any Node.js project will:

  1. Read the license field from package.json (e.g., "MIT" becomes the API key "mit")
  2. Parse the author field (handling both "Jane Doe" and { "name": "Jane Doe" } formats)
  3. Use the current year as default
  4. Fetch the template and write the file

Supporting Multiple License Types

The GitHub API supports over a dozen licenses. Here are the most common ones your CLI can generate:

Key License Best For
mit MIT License Maximum permissiveness, minimal restrictions
apache-2.0 Apache License 2.0 When you need explicit patent grants
gpl-3.0 GNU GPL v3 Ensuring derivative works stay open source
bsd-2-clause BSD 2-Clause Similar to MIT but with different heritage
mpl-2.0 Mozilla Public License 2.0 File-level copyleft (a middle ground)
lgpl-2.1 GNU LGPL v2.1 Libraries that can be used in proprietary software
unlicense The Unlicense Dedicating work to the public domain

To support multi-license projects (some repos include both MIT and Apache-2.0), you can extend the CLI:

async function generateMultiple(keys, fullname, year) {
  for (const key of keys) {
    const license = await fetch(`${API_BASE}/${key}`);
    let body = license.body;
    body = body.replace(/\[year\]/g, year || new Date().getFullYear().toString());
    body = body.replace(/\[fullname\]/g, fullname);

    const filename = keys.length > 1 ? `LICENSE-${key.toUpperCase()}` : 'LICENSE';
    const outputPath = path.join(process.cwd(), filename);
    fs.writeFileSync(outputPath, body);
    console.log(`${license.name} written to ${outputPath}`);
  }
}

// Usage: licensegen --key mit --key apache-2.0 --name "Your Name"
Enter fullscreen mode Exit fullscreen mode

This creates LICENSE-MIT and LICENSE-APACHE-2.0 files — a pattern used by many Rust ecosystem projects.

Making It Global

Add the bin field to your package.json:

{
  "name": "licensegen",
  "version": "1.0.0",
  "bin": {
    "licensegen": "./index.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

Then install globally:

npm link
# or publish
npm publish
Enter fullscreen mode Exit fullscreen mode

Now you can run licensegen --auto in any project directory.

What We Built

In under 100 lines of code, we built a CLI that:

  • Fetches real license templates from GitHub's API (always up-to-date)
  • Auto-detects license type and author from package.json
  • Supports every license GitHub offers
  • Handles multi-license projects
  • Requires zero configuration for most use cases

The broader takeaway: GitHub's API is full of useful endpoints that most developers never explore. The Licenses API is just one example. Before building something from scratch, check if there's an API that does the heavy lifting for you.

Next time you npm init a project, don't forget the LICENSE file. Better yet, automate it so you never have to think about it again.


If you found this useful, check out licensegen on npm — a production-ready version of what we built here.

Top comments (0)