Comparing version 0.2.9 to 0.2.10
@@ -112,2 +112,12 @@ export type MessageType = 'suggestion' | 'warning' | 'error' | ||
| BaseMessage<'DEPRECATED_FIELD_JSNEXT'> | ||
| BaseMessage< | ||
'INVALID_REPOSITORY_VALUE', | ||
{ | ||
type: | ||
| 'invalid-string-shorthand' | ||
| 'invalid-git-url' | ||
| 'deprecated-github-git-protocol' | ||
| 'shorthand-git-sites' | ||
} | ||
> | ||
@@ -114,0 +124,0 @@ export interface Options { |
{ | ||
"name": "publint", | ||
"version": "0.2.9", | ||
"version": "0.2.10", | ||
"description": "Lint packaging errors", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -25,3 +25,7 @@ import { | ||
isRelativePath, | ||
isAbsolutePath | ||
isAbsolutePath, | ||
isGitUrl, | ||
isShorthandRepositoryUrl, | ||
isShorthandGitHubOrGitLabUrl, | ||
isDeprecatedGitHubGitUrl | ||
} from './utils.js' | ||
@@ -238,2 +242,8 @@ | ||
// if `repository` field exist, check if the value is valid | ||
// `repository` might be a shorthand string of URL or an object | ||
if ('repository' in rootPkg) { | ||
promiseQueue.push(() => checkRepositoryField(rootPkg.repository)) | ||
} | ||
// check file existence for other known package fields | ||
@@ -441,3 +451,52 @@ const knownFields = [ | ||
// https://docs.npmjs.com/cli/v10/configuring-npm/package-json#repository | ||
/** | ||
* @param {Record<string, string> | string} repository | ||
*/ | ||
async function checkRepositoryField(repository) { | ||
if (!ensureTypeOfField(repository, ['string', 'object'], ['repository'])) | ||
return | ||
if (typeof repository === 'string') { | ||
// the string field accepts shorthands only. if this doesn't look like a shorthand, | ||
// and looks like a git URL, recommend using the object form. | ||
if (!isShorthandRepositoryUrl(repository)) { | ||
messages.push({ | ||
code: 'INVALID_REPOSITORY_VALUE', | ||
args: { type: 'invalid-string-shorthand' }, | ||
path: ['repository'], | ||
type: 'warning' | ||
}) | ||
} | ||
} else if ( | ||
typeof repository === 'object' && | ||
repository.url && | ||
repository.type === 'git' | ||
) { | ||
if (!isGitUrl(repository.url)) { | ||
messages.push({ | ||
code: 'INVALID_REPOSITORY_VALUE', | ||
args: { type: 'invalid-git-url' }, | ||
path: ['repository', 'url'], | ||
type: 'warning' | ||
}) | ||
} else if (isDeprecatedGitHubGitUrl(repository.url)) { | ||
messages.push({ | ||
code: 'INVALID_REPOSITORY_VALUE', | ||
args: { type: 'deprecated-github-git-protocol' }, | ||
path: ['repository', 'url'], | ||
type: 'suggestion' | ||
}) | ||
} else if (isShorthandGitHubOrGitLabUrl(repository.url)) { | ||
messages.push({ | ||
code: 'INVALID_REPOSITORY_VALUE', | ||
args: { type: 'shorthand-git-sites' }, | ||
path: ['repository', 'url'], | ||
type: 'suggestion' | ||
}) | ||
} | ||
} | ||
} | ||
/** | ||
* @param {string[]} pkgPath | ||
@@ -444,0 +503,0 @@ */ |
@@ -161,2 +161,28 @@ import c from 'picocolors' | ||
return `${c.bold(fp(m.path))} is deprecated. ${c.bold('pkg.module')} should be used instead.` | ||
case 'INVALID_REPOSITORY_VALUE': | ||
switch (m.args.type) { | ||
case 'invalid-string-shorthand': | ||
// prettier-ignore | ||
return `${c.bold(fp(m.path))} is ${c.bold(pv(m.path))} which isn't a valid shorthand value supported by npm. Consider using an object that references a repository.` | ||
case 'invalid-git-url': | ||
// prettier-ignore | ||
return `${c.bold(fp(m.path))} is ${c.bold(pv(m.path))} which isn't a valid git URL. A valid git URL is usually in the form of "${c.bold('git+https://example.com/user/repo.git')}".` | ||
case 'deprecated-github-git-protocol': | ||
// prettier-ignore | ||
return `${c.bold(fp(m.path))} is ${c.bold(pv(m.path))} which uses the git:// protocol that is deprecated by GitHub due to security concerns. Consider replacing the protocol with https://.` | ||
case 'shorthand-git-sites': { | ||
let fullUrl = pv(m.path) | ||
if (fullUrl[fullUrl.length - 1] === '/') { | ||
fullUrl = fullUrl.slice(0, -1) | ||
} | ||
if (!fullUrl.startsWith('git+')) { | ||
fullUrl = 'git+' + fullUrl | ||
} | ||
if (!fullUrl.endsWith('.git')) { | ||
fullUrl += '.git' | ||
} | ||
// prettier-ignore | ||
return `${c.bold(fp(m.path))} is ${c.bold(pv(m.path))} but could be a full git URL like "${c.bold(fullUrl)}".` | ||
} | ||
} | ||
default: | ||
@@ -163,0 +189,0 @@ return |
@@ -9,2 +9,3 @@ import { lintableFileExtensions } from './constants.js' | ||
* exports: Record<string, string>, | ||
* repository: Record<string, string> | string, | ||
* type: 'module' | 'commonjs' | ||
@@ -49,3 +50,56 @@ * }} Pkg | ||
// Reference: https://git-scm.com/docs/git-clone#_git_urls and https://github.com/npm/hosted-git-info | ||
const GIT_URL_RE = | ||
/^(git\+https?|git\+ssh|https?|ssh|git):\/\/(?:[\w._-]+@)?([\w.-]+)(?::([\w\d-]+))?(\/[\w._/-]+)\/?$/ | ||
/** | ||
* @param {string} url | ||
*/ | ||
export function isGitUrl(url) { | ||
return GIT_URL_RE.test(url) | ||
} | ||
/** | ||
* @param {string} url | ||
*/ | ||
export function isShorthandGitHubOrGitLabUrl(url) { | ||
const tokens = url.match(GIT_URL_RE) | ||
if (tokens) { | ||
const host = tokens[2] | ||
const path = tokens[4] | ||
if (/(github|gitlab)/.test(host)) { | ||
return !url.startsWith('git+') || !path.endsWith('.git') | ||
} | ||
} | ||
return false | ||
} | ||
/** | ||
* Reference: https://github.blog/security/application-security/improving-git-protocol-security-github/ | ||
* @param {string} url | ||
*/ | ||
export function isDeprecatedGitHubGitUrl(url) { | ||
const tokens = url.match(GIT_URL_RE) | ||
if (tokens) { | ||
const protocol = tokens[1] | ||
const host = tokens[2] | ||
if (/github/.test(host) && protocol === 'git') { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
// Reference: https://docs.npmjs.com/cli/v10/configuring-npm/package-json#repository | ||
const SHORTHAND_REPOSITORY_URL_RE = | ||
/^(?:(?:github|bitbucket|gitlab):[\w\-]+\/[\w\-]+|gist:\w+|[\w\-]+\/[\w\-]+)$/ | ||
/** | ||
* @param {string} url | ||
*/ | ||
export function isShorthandRepositoryUrl(url) { | ||
return SHORTHAND_REPOSITORY_URL_RE.test(url) | ||
} | ||
/** | ||
* @param {string} code | ||
@@ -52,0 +106,0 @@ * @returns {CodeFormat} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
78776
2064