Socket
Socket
Sign inDemoInstall

publint

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

publint - npm Package Compare versions

Comparing version 0.2.2 to 0.2.3

src/constants.js

23

index.d.ts

@@ -47,2 +47,6 @@ export type MessageType = 'suggestion' | 'warning' | 'error'

>
| BaseMessage<
'FILE_INVALID_JSX_EXTENSION',
{ actualExtension: string; globbedFilePath?: string }
>
| BaseMessage<'FILE_DOES_NOT_EXIST'>

@@ -68,2 +72,3 @@ | BaseMessage<'FILE_NOT_PUBLISHED'>

| BaseMessage<'USE_EXPORTS_OR_IMPORTS_BROWSER'>
| BaseMessage<'USE_FILES'>
| BaseMessage<

@@ -88,2 +93,20 @@ 'TYPES_NOT_EXPORTED',

>
| BaseMessage<
'FIELD_INVALID_VALUE_TYPE',
{
actualType: string
expectTypes: string[]
}
>
| BaseMessage<
'EXPORTS_VALUE_CONFLICTS_WITH_BROWSER',
{
/**
* The path to the key inside the `"browser"` field that conflicts with
* the current path `"exports"` field
*/
browserPath: string[]
browserishCondition: string
}
>

@@ -90,0 +113,0 @@ export interface Options {

2

package.json
{
"name": "publint",
"version": "0.2.2",
"version": "0.2.3",
"description": "Lint packaging errors",

@@ -5,0 +5,0 @@ "type": "module",

import {
commonInternalPaths,
invalidJsxExtensions,
knownBrowserishConditions
} from './constants.js'
import {
exportsGlob,

@@ -50,51 +55,17 @@ getCodeFormat,

/**
* @param {string} path file path to read
* @param {string[]} [pkgPath] current path that tries to read this file.
* pass `undefined` to prevent error reporting if the file is missing.
* @param {string[]} tryExtensions list of extensions to try before giving up
* @returns {Promise<string | false>}
*/
async function readFile(path, pkgPath = undefined, tryExtensions = []) {
try {
const content = await vfs.readFile(path)
if (pkgPath && _packedFiles && !_packedFiles.includes(path)) {
fileNotPublished(pkgPath)
}
return content
} catch {
for (let ext of tryExtensions) {
// remove duplicated slashes
if (ext[0] === '/' && path[path.length - 1] === '/') {
ext = ext.slice(1)
// Check if package published internal tests or config files
if (rootPkg.files == null) {
promiseQueue.push(async () => {
for (const p of commonInternalPaths) {
const internalPath = vfs.pathJoin(pkgDir, p)
if (await vfs.isPathExist(internalPath)) {
messages.push({
code: 'USE_FILES',
args: {},
path: ['name'],
type: 'suggestion'
})
break
}
try {
const content = await vfs.readFile(path + ext)
if (pkgPath && _packedFiles && !_packedFiles.includes(path)) {
fileNotPublished(pkgPath)
}
return content
} catch {}
}
if (pkgPath) {
messages.push({
code: 'FILE_DOES_NOT_EXIST',
args: {},
path: pkgPath,
type: 'error'
})
}
return false
}
}
/**
* @param {string[]} pkgPath
*/
function fileNotPublished(pkgPath) {
messages.push({
code: 'FILE_NOT_PUBLISHED',
args: {},
path: pkgPath,
type: 'error'
})

@@ -106,3 +77,3 @@ }

// LOAD_INDEX(X)
if (!main && !module && !exports) {
if (main == null && module == null && exports == null) {
promiseQueue.push(async () => {

@@ -140,4 +111,5 @@ // check index.js only, others aren't our problem

*/
if (main) {
if (main != null) {
promiseQueue.push(async () => {
if (!ensureTypeOfField(main, ['string'], mainPkgPath)) return
const mainPath = vfs.pathJoin(pkgDir, main)

@@ -149,2 +121,4 @@ const mainContent = await readFile(mainPath, mainPkgPath, [

if (mainContent === false) return
if (hasInvalidJsxExtension(main, mainPkgPath)) return
if (!isFilePathLintable(main)) return
const actualFormat = getCodeFormat(mainContent)

@@ -172,3 +146,3 @@ const expectFormat = await getFilePathFormat(mainPath, vfs)

}
if (expectFormat === 'ESM' && !exports) {
if (actualFormat === 'ESM' && exports == null) {
messages.push({

@@ -190,4 +164,5 @@ code: 'HAS_ESM_MAIN_BUT_NO_EXPORTS',

*/
if (module) {
if (module != null) {
promiseQueue.push(async () => {
if (!ensureTypeOfField(module, ['string'], modulePkgPath)) return
const modulePath = vfs.pathJoin(pkgDir, module)

@@ -199,2 +174,4 @@ const moduleContent = await readFile(modulePath, modulePkgPath, [

if (moduleContent === false) return
if (hasInvalidJsxExtension(module, modulePkgPath)) return
if (!isFilePathLintable(module)) return
const actualFormat = getCodeFormat(moduleContent)

@@ -238,3 +215,6 @@ if (actualFormat === 'CJS') {

const [fieldValue, fieldPkgPath] = getPublishedField(rootPkg, field)
if (typeof fieldValue === 'string') {
if (
fieldValue != null &&
ensureTypeOfField(fieldValue, ['string'], fieldPkgPath)
) {
promiseQueue.push(async () => {

@@ -287,2 +267,10 @@ const fieldPath = vfs.pathJoin(pkgDir, fieldValue)

for (const filePath of files) {
if (
hasInvalidJsxExtension(
filePath,
['name'],
'/' + vfs.pathRelative(pkgDir, filePath)
)
)
continue
if (!isFilePathLintable(filePath)) continue

@@ -360,2 +348,98 @@ pq.push(async () => {

/**
* @param {string} path file path to read
* @param {string[]} [pkgPath] current path that tries to read this file.
* pass `undefined` to prevent error reporting if the file is missing.
* @param {string[]} tryExtensions list of extensions to try before giving up
* @returns {Promise<string | false>}
*/
async function readFile(path, pkgPath = undefined, tryExtensions = []) {
try {
const content = await vfs.readFile(path)
if (pkgPath && _packedFiles && !_packedFiles.includes(path)) {
fileNotPublished(pkgPath)
}
return content
} catch {
for (let ext of tryExtensions) {
// remove duplicated slashes
if (ext[0] === '/' && path[path.length - 1] === '/') {
ext = ext.slice(1)
}
try {
const content = await vfs.readFile(path + ext)
if (pkgPath && _packedFiles && !_packedFiles.includes(path)) {
fileNotPublished(pkgPath)
}
return content
} catch {}
}
if (pkgPath) {
messages.push({
code: 'FILE_DOES_NOT_EXIST',
args: {},
path: pkgPath,
type: 'error'
})
}
return false
}
}
/**
* @param {string[]} pkgPath
*/
function fileNotPublished(pkgPath) {
messages.push({
code: 'FILE_NOT_PUBLISHED',
args: {},
path: pkgPath,
type: 'error'
})
}
/**
* @param {string} filePath
* @param {string[]} currentPath
* @param {string} [globbedFilePath] only needed for globs
*/
function hasInvalidJsxExtension(filePath, currentPath, globbedFilePath) {
const matched = invalidJsxExtensions.find((ext) => filePath.endsWith(ext))
if (matched) {
messages.push({
code: 'FILE_INVALID_JSX_EXTENSION',
args: {
actualExtension: matched,
globbedFilePath
},
path: currentPath,
type: 'error'
})
return true
}
return false
}
/**
* @param {any} fieldValue
* @param {('string' | 'number' | 'boolean' | 'object')[]} expectTypes
* @param {string[]} pkgPath
*/
function ensureTypeOfField(fieldValue, expectTypes, pkgPath) {
// @ts-expect-error typeof doesn't need to match `expectedTypes` type but TS panics
if (!expectTypes.includes(typeof fieldValue)) {
messages.push({
code: 'FIELD_INVALID_VALUE_TYPE',
args: {
actualType: typeof fieldValue,
expectTypes
},
path: pkgPath,
type: 'error'
})
return false
}
return true
}
/**
* @param {string | Record<string, any>} fieldValue

@@ -457,2 +541,22 @@ * @param {string[]} currentPath

// if the exports value matches a key in `pkg.browser` (meaning it'll be remapped
// if in a browser-ish environment), check if this is a browser-ish environment/condition.
// if so, warn about this conflict as it's often unexpected behaviour.
if (typeof browser === 'object' && exportsValue in browser) {
const browserishCondition = knownBrowserishConditions.find((c) =>
currentPath.includes(c)
)
if (browserishCondition) {
messages.push({
code: 'EXPORTS_VALUE_CONFLICTS_WITH_BROWSER',
args: {
browserPath: browserPkgPath.concat(exportsValue),
browserishCondition
},
path: currentPath,
type: 'warning'
})
}
}
const pq = createPromiseQueue()

@@ -462,2 +566,10 @@

for (const filePath of exportsFiles) {
if (
hasInvalidJsxExtension(
filePath,
currentPath,
isGlob ? './' + vfs.pathRelative(pkgDir, filePath) : undefined
)
)
return
// TODO: maybe check .ts in the future

@@ -507,8 +619,10 @@ if (!isFilePathLintable(filePath)) continue

// NOTE: only relax this for globbed files, as they're implicitly exported.
const expectFilePath = replaceLast(
filePath,
actualExtension,
expectExtension
)
if (await vfs.isPathExist(expectFilePath)) return
if (isGlob) {
const expectFilePath = replaceLast(
filePath,
actualExtension,
expectExtension
)
if (await vfs.isPathExist(expectFilePath)) return
}

@@ -515,0 +629,0 @@ messages.push({

@@ -39,2 +39,12 @@ import c from 'picocolors'

}
case 'FILE_INVALID_JSX_EXTENSION': {
const is = m.args.globbedFilePath ? 'matches' : 'is'
const relativePath = m.args.globbedFilePath ?? pv(m.path)
const start =
m.path[0] === 'name'
? c.bold(relativePath)
: `${c.bold(fp(m.path))} ${is} ${c.bold(relativePath)} which`
// prettier-ignore
return `${start} uses an invalid ${c.bold(m.args.actualExtension)} extension. You don't need to split ESM and CJS formats for JSX. You should write a single file in ESM with the ${c.bold('.jsx')} extension instead, e.g. ${c.bold(replaceLast(pv(m.path), m.args.actualExtension, '.jsx'))}`
}
case 'FILE_DOES_NOT_EXIST':

@@ -84,2 +94,5 @@ // prettier-ignore

return `${c.bold('pkg.browser')} with an object value can be refactored to use ${c.bold('pkg.exports')}/${c.bold('pkg.imports')} and the ${c.bold('"browser"')} condition to declare browser-specific exports. (This will be a breaking change)`
case 'USE_FILES':
// prettier-ignore
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.`
case 'TYPES_NOT_EXPORTED': {

@@ -124,2 +137,17 @@ const typesFilePath = exportsRel(m.args.typesFilePath)

}
case 'FIELD_INVALID_VALUE_TYPE': {
let expectStr = m.args.expectTypes[0]
for (let i = 1; i < m.args.expectTypes.length; i++) {
if (i === m.args.expectTypes.length - 1) {
expectStr += ` or ${m.args.expectTypes[i]}`
} else {
expectStr += `, ${m.args.expectTypes[i]}`
}
}
// prettier-ignore
return `${c.bold(fp(m.path))} is ${c.bold(pv(m.path))} which is an invalid ${c.bold(m.args.actualType)} type. Expected a ${c.bold(expectStr)} type instead.`
}
case 'EXPORTS_VALUE_CONFLICTS_WITH_BROWSER':
// prettier-ignore
return `${c.bold(fp(m.path))} is ${c.bold(pv(m.path))} which also matches ${c.bold(fp(m.args.browserPath))}: "${c.bold(pv(m.args.browserPath))}", which overrides the path when building the library with the "${c.bold(m.args.browserishCondition)}" condition. This is usually unintentional and may cause build issues. Consider using a different file name for ${c.bold(pv(m.path))}.`
default:

@@ -126,0 +154,0 @@ return

@@ -0,1 +1,3 @@

import { lintableFileExtensions } from './constants.js'
/**

@@ -216,7 +218,3 @@ * @typedef {{

export function isFilePathLintable(filePath) {
return (
filePath.endsWith('.js') ||
filePath.endsWith('.mjs') ||
filePath.endsWith('.cjs')
)
return lintableFileExtensions.some((ext) => filePath.endsWith(ext))
}

@@ -223,0 +221,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc