Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/node-modules-tools/src/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { readFile } from 'node:fs/promises'
import { join } from 'pathe'
import { resolvePackageJsonFields } from './resolve-json'
import { getPackageInstallSize } from './size'
import { detectLicenseFromFile } from './utils/package-json'

/**
* Analyze a package node, and return a resolved package node.
Expand All @@ -28,8 +29,16 @@ export async function resolvePackage(
const content = await readFile(path, 'utf-8')
const json = JSON.parse(stripBomTag(content)) as PackageJson

const resolved = resolvePackageJsonFields(json)

// Fallback: when package.json has no license field, try to detect
// the license from a LICENSE file in the package directory.
if (!resolved.license) {
resolved.license = detectLicenseFromFile(pkg.filepath)
}

_pkg.resolved = {
...resolvePackageJsonFields(json),
...resolved,
installSize: await getPackageInstallSize(_pkg),
}
}
Expand Down
30 changes: 30 additions & 0 deletions packages/node-modules-tools/src/utils/package-json.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import type { PackageJson } from '../types'
import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import { toArray } from '@antfu/utils'

const RE_GITHUB_SPONSORS = /^(?:https?:\/\/)?(?:www\.)?github\.com\/sponsors\/([\w.-]+)/i
const RE_GITHUB_USER_URL = /^(?:https?:\/\/)?(?:www\.)?github\.com\/([\w.-]+)\/?$/i
const RE_LICENSE_NAMES = /^(?:LICENSE|LICENCE|COPYING|NOTICE)(?:\.(?:md|txt))?$/i
const LICENSE_PATTERNS: [RegExp, string][] = [
[/Permission is hereby granted.*?without restriction/i, 'MIT'],
[/ISC License/i, 'ISC'],
[/Apache License,?\s*Version 2\.0/i, 'Apache-2.0'],
[/Redistribution and use in source and binary forms.*?provided that the following conditions are met/i, 'BSD-3-Clause'],
[/Redistribution and use in source and binary forms.*?provided that the following conditions are met.*?Neither the name/i, 'BSD-3-Clause'],
[/Redistribution and use in source and binary forms.*?provided that the following conditions are met(?!.*Neither)/i, 'BSD-2-Clause'],
[/UNIVERSAL PUBLIC LICENSE/i, 'UPL-1.0'],
]
const RE_GITHUB_PROFILE_LIKE = /^(?:https?:\/\/)?(?:www\.)?github\.com\/([\w.-]+)/i
const RE_GITHUB_NOREPLY = /^(?:\d+\+)?([\w.-]+)@users\.noreply\.github\.com$/i
const RE_OPENCOLLECTIVE = /^(?:https?:\/\/)?(?:www\.)?opencollective\.com\/([\w.-]+)/i
Expand Down Expand Up @@ -126,6 +138,24 @@ export function normalizePkgFundings(json: PackageJson): ParsedFunding[] | undef

return fundings.map(f => parseFunding(f)).filter(Boolean)
}
/**
* Detect the SPDX license identifier from a LICENSE file in the given directory.
*/

export function detectLicenseFromFile(dir: string): string | undefined {
if (!existsSync(dir))
return undefined

const files = readdirSync(dir)
const licenseFile = files.find(f => RE_LICENSE_NAMES.test(f))
if (!licenseFile)
return undefined
const content = readFileSync(join(dir, licenseFile), 'utf-8')
for (const [pattern, spdx] of LICENSE_PATTERNS) {
if (pattern.test(content))
return spdx
}
}

export interface RawAuthor {
name?: string
Expand Down
Loading