/
getPkgInfo.ts
131 lines (116 loc) · 3.47 KB
/
getPkgInfo.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import path from 'node:path'
import fs from 'node:fs/promises'
import { readPackageJson } from '@pnpm/read-package-json'
import pLimit from 'p-limit'
import { PackageManifest } from '@pnpm/types'
const limitPkgReads = pLimit(4)
export async function readPkg (pkgPath: string) {
return limitPkgReads(async () => readPackageJson(pkgPath))
}
/**
* @const
* List of typical names for license files
*/
const LICENSE_FILES = ['./LICENSE', './LICENCE']
export type LicenseInfo = {
name: string
licenseFile?: string
}
/**
* Coerce the given value to a string or a null value
* @param field the string to be converted
* @returns string | null
*/
function coerceToString (field: unknown): string | null {
const string = String(field)
return typeof field === 'string' || field === string ? string : null
}
function parseLicenseManifestField (field: unknown) {
if (Array.isArray(field)) {
const licenses = field
const licenseTypes = licenses.reduce((listOfLicenseTypes, license) => {
const type = coerceToString(license.type)
if (type) {
listOfLicenseTypes.push(type)
}
return listOfLicenseTypes
}, [])
if (licenseTypes.length > 1) {
const combinedLicenseTypes = licenseTypes.join(' OR ') as string
return `(${combinedLicenseTypes})`
}
return licenseTypes[0] ?? null
} else {
return (field as { type: string })?.type ?? coerceToString(field)
}
}
/**
*
* @param {*} packageInfo
* @returns
*/
async function parseLicense (packageInfo: {
manifest: PackageManifest
path: string
}): Promise<LicenseInfo> {
const license = parseLicenseManifestField(packageInfo.manifest.license)
// check if we discovered a license, if not attempt to parse the LICENSE file
if (
(!license || /see license/i.test(license))
) {
for (const filename of LICENSE_FILES) {
try {
const licensePath = path.join(packageInfo.path, filename)
const licenseContents = await fs.readFile(licensePath)
return {
name: 'Unknown',
licenseFile: licenseContents.toString('utf-8'),
}
} catch (err) {
// NOOP
}
}
}
return { name: license ?? 'Unknown' }
}
export async function getPkgInfo (
pkg: {
alias: string
name: string
version: string
prefix: string
}
) {
let manifest
let packageModulePath
let licenseInfo: LicenseInfo
try {
packageModulePath = path.join(pkg.prefix, 'node_modules', pkg.name)
const packageManifestPath = path.join(packageModulePath, 'package.json')
manifest = await readPkg(packageManifestPath)
licenseInfo = await parseLicense({ manifest, path: packageModulePath })
manifest.license = licenseInfo.name
} catch (err: any) { // eslint-disable-line
// This will probably never happen
throw new Error(`Failed to fetch manifest data for ${pkg.name}`)
}
return {
packageManifest: manifest,
packageInfo: {
alias: pkg.alias,
from: pkg.name,
path: packageModulePath,
version: pkg.version,
description: manifest.description,
license: licenseInfo.name,
licenseContents: licenseInfo.licenseFile,
author: (manifest.author && (
typeof manifest.author === 'string' ? manifest.author : (manifest.author as { name: string }).name
)) ?? undefined,
homepage: manifest.homepage,
repository: (manifest.repository && (
typeof manifest.repository === 'string' ? manifest.repository : manifest.repository.url
)) ?? undefined,
},
}
}