Skip to content

Commit 6f2e4f2

Browse files
committed
Check license field is set if license file exists
close #111
1 parent 4525806 commit 6f2e4f2

File tree

9 files changed

+49
-2
lines changed

9 files changed

+49
-2
lines changed

pkg/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export type Message =
7171
| BaseMessage<'USE_EXPORTS_OR_IMPORTS_BROWSER'>
7272
| BaseMessage<'USE_FILES'>
7373
| BaseMessage<'USE_TYPE'>
74+
| BaseMessage<'USE_LICENSE', { licenseFilePath: string }>
7475
| BaseMessage<
7576
'TYPES_NOT_EXPORTED',
7677
{

pkg/src/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ export const commonInternalPaths = [
3030
'.eslintrc',
3131
'.eslintrc.js'
3232
]
33+
34+
// https://github.com/npm/npm-packlist/blob/53b2a4f42b7fef0f63e8f26a3ea4692e23a58fed/lib/index.js#L284-L286
35+
export const licenseFiles = [/^copying/i, /^licence/i, /^license/i]

pkg/src/index.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {
22
commonInternalPaths,
33
invalidJsxExtensions,
4-
knownBrowserishConditions
4+
knownBrowserishConditions,
5+
licenseFiles
56
} from './constants.js'
67
import {
78
exportsGlob,
@@ -82,6 +83,30 @@ export async function publint({ pkgDir, vfs, level, strict, _packedFiles }) {
8283
})
8384
}
8485

86+
// Check if has license file but no license field
87+
if (rootPkg.license == null) {
88+
promiseQueue.push(async () => {
89+
const topFiles = await vfs.readDir(pkgDir)
90+
/** @type {string | undefined} */
91+
let matchedLicenseFilePath
92+
for (const f of topFiles) {
93+
if (await vfs.isPathDir(vfs.pathJoin(pkgDir, f))) continue
94+
if (licenseFiles.some((r) => r.test(f))) {
95+
matchedLicenseFilePath = '/' + f
96+
break
97+
}
98+
}
99+
if (matchedLicenseFilePath) {
100+
messages.push({
101+
code: 'USE_LICENSE',
102+
args: { licenseFilePath: matchedLicenseFilePath },
103+
path: ['name'],
104+
type: 'suggestion'
105+
})
106+
}
107+
})
108+
}
109+
85110
// Check if "type" field is specified, help Node.js push towards an ESM default future:
86111
// https://nodejs.org/en/blog/release/v20.10.0
87112
if (rootPkg.type == null) {

pkg/src/message.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ export function formatMessage(m, pkg, opts = {}) {
114114
return `The package ${c.bold('publishes internal tests or config files')}. You can use ${c.bold('pkg.files')} to only publish certain files and save user bandwidth.`
115115
case 'USE_TYPE':
116116
// prettier-ignore
117-
return `The package does not specify the ${c.bold('type')} field. NodeJS may attempt to detect the package type causing a small performance hit. Consider adding ${c.bold('"type"')}: "${c.bold('commonjs')}".`
117+
return `The package does not specify the ${c.bold('"type"')} field. NodeJS may attempt to detect the package type causing a small performance hit. Consider adding ${c.bold('"type"')}: "${c.bold('commonjs')}".`
118+
case 'USE_LICENSE':
119+
// prettier-ignore
120+
return `The package does not specify the ${c.bold('"license"')} field but a license file was detected at ${c.bold(m.args.licenseFilePath)}. Consider adding a ${c.bold('"license"')} field so it's displayed on npm.`
118121
case 'TYPES_NOT_EXPORTED': {
119122
const typesFilePath = exportsRel(m.args.typesFilePath)
120123
if (m.args.actualExtension && m.args.expectExtension) {

pkg/tests/fixtures/missing-license/license

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "publint-missing-license",
3+
"version": "0.0.1",
4+
"private": true,
5+
"type": "commonjs"
6+
}

pkg/tests/playground.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ testFixture('missing-files', [
4444
'USE_EXPORTS_OR_IMPORTS_BROWSER'
4545
])
4646

47+
testFixture('missing-license', ['USE_LICENSE'])
48+
4749
testFixture('no-exports-module', [])
4850

4951
testFixture('not-missing-files', [])

site/rules.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ Internal tests or config files are published, which are usually not needed and u
206206

207207
[Node.js v21.1.0](https://nodejs.org/en/blog/release/v21.1.0) adds a new `--experimental-detect-module`, which can be used to automatically run ES modules when ESM syntax can be detected. Node.js hopes to make detection enabled by default in the future. Detection increases startup time, so Node is encouraging everyone — especially package authors — to add a type field to `package.json`, even for the default `"type": "commonjs"`.
208208

209+
## `USE_LICENSE`
210+
211+
A license file is published but the `"license"` field is not set in `package.json`. Consider adding a `"license"` field so that it's correctly displayed on npm and allows other tooling to parse the package license.
212+
209213
## `FIELD_INVALID_VALUE_TYPE`
210214

211215
Some `package.json` fields has a set of allowed types, e.g. `string` or `object` only. If an invalid type is passed, this error message will be showed.

site/src/utils/message.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ function messageToString(m, pkg) {
9595
case 'USE_TYPE':
9696
// prettier-ignore
9797
return `The package does not specify the ${bold('"type"')} field. NodeJS may attempt to detect the package type causing a small performance hit. Consider adding ${bold('"type"')}: "${bold('commonjs')}".`
98+
case 'USE_LICENSE':
99+
// prettier-ignore
100+
return `The package does not specify the ${bold('"license"')} field but a license file was detected at ${bold(m.args.licenseFilePath)}. Consider adding a ${bold('"license"')} field so it's displayed on npm.`
98101
case 'TYPES_NOT_EXPORTED': {
99102
const typesFilePath = exportsRel(m.args.typesFilePath)
100103
if (m.args.actualExtension && m.args.expectExtension) {

0 commit comments

Comments
 (0)