Comparing version 0.0.1 to 0.1.0
@@ -22,2 +22,3 @@ export type MessageType = 'suggestion' | 'warning' | 'error' | ||
// TODO: Check nodejs modules usage | ||
export type Message = | ||
@@ -53,4 +54,9 @@ | BaseMessage< | ||
| BaseMessage<'EXPORTS_GLOB_NO_MATCHED_FILES'> | ||
// TODO | ||
// | BaseMessage<'EXPORTS_GLOB_NO_MATCHED_FILES_BUT_IMPLICIT_INDEX_DETECTED'> | ||
| BaseMessage< | ||
'EXPORTS_GLOB_NO_DEPRECATED_SUBPATH_MAPPING', | ||
{ | ||
expectPath: string[] | ||
expectValue: string | ||
} | ||
> | ||
| BaseMessage<'EXPORTS_TYPES_SHOULD_BE_FIRST'> | ||
@@ -57,0 +63,0 @@ | BaseMessage<'EXPORTS_DEFAULT_SHOULD_BE_LAST'> |
{ | ||
"name": "publint", | ||
"version": "0.0.1", | ||
"version": "0.1.0", | ||
"description": "Lint packaging errors", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -1,5 +0,23 @@ | ||
# publint | ||
<br> | ||
Lint before you publish! Catch packaging errors and ensure compatibility across environments. | ||
<p align="center"> | ||
<img src="https://user-images.githubusercontent.com/34116392/172312754-0407aeaa-d7a6-4ada-8bc0-ea80bc314f5f.svg" height="80"> | ||
</p> | ||
<h1 align="center"> | ||
publint | ||
</h1> | ||
<p align="center"> | ||
Lint packaging errors. Ensure compatibility across environments. | ||
</p> | ||
<p align="center"> | ||
<a href="https://publint.bjornlu.com"> | ||
<strong>Try it online</strong> | ||
</a> | ||
</p> | ||
<br> | ||
This package contains a CLI and API to lint packages locally. The package to be linted must exist and be built locally for the lint to succeed. To test other npm packages, try https://publint.bjornlu.com. | ||
@@ -28,3 +46,3 @@ | ||
const messages = await publint({ | ||
/** | ||
/** | ||
* Path to your package that contains a package.json file. | ||
@@ -31,0 +49,0 @@ * Defaults to `process.cwd()` in node, `/` in browser. |
@@ -16,2 +16,5 @@ import { | ||
const messages = [] | ||
/** | ||
* A promise queue is created to run all linting tasks in parallel | ||
*/ | ||
const promiseQueue = createPromiseQueue() | ||
@@ -26,2 +29,13 @@ | ||
/** | ||
* @param {string} filePath | ||
*/ | ||
function isPathLintable(filePath) { | ||
return ( | ||
filePath.endsWith('.js') || | ||
filePath.endsWith('.mjs') || | ||
filePath.endsWith('.cjs') | ||
) | ||
} | ||
/** | ||
* @param {string} path | ||
@@ -74,3 +88,3 @@ * @param {string[]} pkgPath | ||
}, | ||
path: [], | ||
path: ['name'], | ||
type: 'warning' | ||
@@ -169,2 +183,39 @@ }) | ||
crawlExports(exports) | ||
} else { | ||
// all files can be accessed. verify them all | ||
promiseQueue.push(async () => { | ||
const files = await exportsGlob(vfs.pathJoin(pkgDir, './*'), vfs) | ||
const pq = createPromiseQueue() | ||
for (const filePath of files) { | ||
if (!isPathLintable(filePath)) continue | ||
pq.push(async () => { | ||
const fileContent = await readFile(filePath, []) | ||
if (fileContent === false) return | ||
const actualFormat = getCodeFormat(fileContent) | ||
let expectFormat = await getFilePathFormat(filePath, vfs) | ||
if ( | ||
actualFormat !== expectFormat && | ||
actualFormat !== 'unknown' && | ||
actualFormat !== 'mixed' | ||
) { | ||
const actualExtension = vfs.getExtName(filePath) | ||
messages.push({ | ||
code: isExplicitExtension(actualExtension) | ||
? 'FILE_INVALID_EXPLICIT_FORMAT' | ||
: 'FILE_INVALID_FORMAT', | ||
args: { | ||
actualFormat, | ||
expectFormat, | ||
actualExtension, | ||
expectExtension: getCodeFormatExtension(actualFormat), | ||
actualFilePath: '/' + vfs.pathRelative(pkgDir, filePath) | ||
}, | ||
path: ['name'], | ||
type: 'warning' | ||
}) | ||
} | ||
}) | ||
} | ||
await pq.wait() | ||
}) | ||
} | ||
@@ -186,2 +237,20 @@ | ||
promiseQueue.push(async () => { | ||
// https://nodejs.org/docs/latest-v16.x/api/packages.html#subpath-folder-mappings | ||
if (exports.endsWith('/')) { | ||
const expectPath = currentPath.map((part) => { | ||
return part.endsWith('/') ? part + '*' : part | ||
}) | ||
messages.push({ | ||
code: 'EXPORTS_GLOB_NO_DEPRECATED_SUBPATH_MAPPING', | ||
args: { | ||
expectPath, | ||
expectValue: exports + '*' | ||
}, | ||
path: currentPath, | ||
type: 'warning' | ||
}) | ||
// Help fix glob so we can further analyze other issues | ||
exports += '*' | ||
} | ||
const exportsPath = vfs.pathJoin(pkgDir, exports) | ||
@@ -196,5 +265,3 @@ const isGlob = exports.includes('*') | ||
code: 'EXPORTS_GLOB_NO_MATCHED_FILES', | ||
args: { | ||
pattern: exports | ||
}, | ||
args: {}, | ||
path: currentPath, | ||
@@ -208,5 +275,6 @@ type: 'warning' | ||
// todo: group glob warnings | ||
// TODO: group glob warnings | ||
for (const filePath of exportsFiles) { | ||
if (filePath.endsWith('.d.ts')) continue | ||
// TODO: Maybe check .ts in the future | ||
if (!isPathLintable(filePath)) continue | ||
pq.push(async () => { | ||
@@ -213,0 +281,0 @@ // Could fail if in !isGlob |
@@ -30,3 +30,3 @@ import c from 'picocolors' | ||
// prettier-ignore | ||
return `${c.bold(fp(m.path))} is ${pv(m.path)} but file does not exist` | ||
return `${c.bold(fp(m.path))} is ${pv(m.path)} but file does not exist.` | ||
case 'HAS_ESM_MAIN_BUT_NO_EXPORTS': | ||
@@ -39,3 +39,2 @@ // prettier-ignore | ||
case 'MODULE_SHOULD_BE_ESM': | ||
// TODO: Show how we know this? Likely case is `type: module` | ||
// prettier-ignore | ||
@@ -45,3 +44,6 @@ return `${c.bold('pkg.module')} should be ESM, but the code is written in CJS.` | ||
// prettier-ignore | ||
return `${c.bold(fp(m.path))} is ${c.bold(pv(m.path))} but does not match any files` | ||
return `${c.bold(fp(m.path))} is ${c.bold(pv(m.path))} but does not match any files.` | ||
case 'EXPORTS_GLOB_NO_DEPRECATED_SUBPATH_MAPPING': | ||
// prettier-ignore | ||
return `${c.bold(fp(m.path))} maps to a path that ends with ${c.bold('/')} which is deprecated. Use ${c.bold(fp(m.args.expectPath))}: "${c.bold(m.args.expectValue)}" instead.` | ||
case 'EXPORTS_TYPES_SHOULD_BE_FIRST': | ||
@@ -48,0 +50,0 @@ // prettier-ignore |
@@ -70,5 +70,10 @@ /** | ||
const [dir, ext] = globStr.split('*') | ||
await scanDir(dir) | ||
if (await vfs.isPathDir(dir)) { | ||
await scanDir(dir) | ||
} | ||
return filePaths | ||
/** | ||
* @param {string} dirPath | ||
*/ | ||
async function scanDir(dirPath) { | ||
@@ -143,3 +148,3 @@ const items = await vfs.readDir(dirPath) | ||
export function formatMessagePath(path) { | ||
let formatted = 'pkg.' | ||
let formatted = 'pkg' | ||
for (const part of path) { | ||
@@ -150,3 +155,3 @@ // is invalid js var name | ||
} else { | ||
formatted += part | ||
formatted += '.' + part | ||
} | ||
@@ -153,0 +158,0 @@ } |
@@ -18,3 +18,7 @@ import fs from 'node:fs' | ||
async isPathDir(path) { | ||
return (await fsp.stat(path)).isDirectory() | ||
try { | ||
return (await fsp.stat(path)).isDirectory() | ||
} catch { | ||
return false | ||
} | ||
}, | ||
@@ -21,0 +25,0 @@ async isPathExist(path) { |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
26981
729
64
0