forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import spdxLicenseIds from 'spdx-license-list/spdx-simple.json'
2
3/**
4 * Set of all valid SPDX license identifiers.
5 * Sourced from spdx-license-list package which stays up-to-date with
6 * the official SPDX license list.
7 *
8 * @see https://spdx.org/licenses/
9 */
10export const SPDX_LICENSE_IDS: Set<string> = new Set(spdxLicenseIds)
11
12/**
13 * Check if a license identifier is a valid SPDX license.
14 * @see https://spdx.org/licenses/
15 */
16export function isValidSpdxLicense(license: string): boolean {
17 return SPDX_LICENSE_IDS.has(license)
18}
19
20/**
21 * Generate an SPDX license URL for the given license identifier.
22 * Returns null if the license is not a valid SPDX identifier.
23 * @see https://spdx.org/licenses/
24 */
25export function getSpdxLicenseUrl(license: string | undefined): string | null {
26 if (!license) return null
27 const trimmed = license.trim()
28 if (!SPDX_LICENSE_IDS.has(trimmed)) return null
29 return `https://spdx.org/licenses/${trimmed}.html`
30}
31
32/**
33 * Token types for parsed license expressions
34 */
35export interface LicenseToken {
36 type: 'license' | 'operator'
37 value: string
38 url?: string
39}
40
41/**
42 * Parse an SPDX license expression into tokens.
43 * Handles compound expressions like "MIT OR Apache-2.0", "(MIT AND Zlib)".
44 * Strips parentheses for cleaner display.
45 *
46 * @example
47 * parseLicenseExpression('MIT') // [{ type: 'license', value: 'MIT', url: '...' }]
48 * parseLicenseExpression('MIT OR Apache-2.0')
49 * // [{ type: 'license', value: 'MIT', url: '...' }, { type: 'operator', value: 'OR' }, { type: 'license', value: 'Apache-2.0', url: '...' }]
50 */
51export function parseLicenseExpression(expression: string): LicenseToken[] {
52 const result: LicenseToken[] = []
53 // Match operators first (OR, AND, WITH), then license IDs - ignore parentheses
54 // Operators must be checked first to avoid being captured as license identifiers
55 const pattern = /\b(OR|AND|WITH)\b|([A-Za-z0-9.\-+]+)/g
56 let match
57
58 while ((match = pattern.exec(expression)) !== null) {
59 if (match[1]) {
60 // Operator (OR, AND, WITH)
61 result.push({ type: 'operator', value: match[1] })
62 } else if (match[2]) {
63 // License identifier
64 const id = match[2]
65 const isValid = isValidSpdxLicense(id)
66 result.push({
67 type: 'license',
68 value: id,
69 url: isValid ? `https://spdx.org/licenses/${id}.html` : undefined,
70 })
71 }
72 }
73
74 return result
75}