See the usage section for more information.
- This opinionated CLI tool checks a Node.js project for correctness. It should be run after the project has been built. TypeScript (tsc) is used for type checking, ESLint and Babel for static analysis of JavaScript/TypeScript build output, and Remark and mdast for analysis of Markdown source. Further checks are performed to ensure the project is optimally structured and conforms to best practices, including detecting when running in a monorepo root vs a polyrepo root vs a sub-root.
npm install --save-dev @projector-js/plugin-lint
Standalone usage:
npx @projector-js/plugin-lint
Or, if configured to work with Projector:
npm run lint
Help text (use --help
to get the most up-to-date version):
plugin-lint
Check a project for correctness.
Options:
--help Show help [boolean]
--version Show version number [boolean]
--silent Nothing will be printed to stdout or stderr
[boolean] [default: false]
--root-dir The project root directory containing ESLint and
TypeScript configuration files, and that relative
paths and globs are resolved against. This must be an
absolute path. [string] [default: process.cwd()]
--md-path Absolute paths, relative paths, and/or globs that
resolve to one or more markdown files. If a single
argument ending in "/" is given, the default glob
pattern will be appended to this argument instead.
[array] [default: .md files not under node_modules]
--project An absolute or relative path to, or file name of, a
TypeScript tsconfig.json configuration file. Source
paths are determined using this file's "files,"
"include," and "exclude" fields with all file
extensions recognized by the TypeScript compiler
considered. [string] [default: "tsconfig.lint.json"]
--pre-push-only In pre-push mode, a limited subset of checks are
performed. Pre-push linting mode is meant to be
invoked by the "pre-push" Git hook.
[boolean] [default: false]
--link-protection-only In link protection mode, a limited subset of checks
are performed. Link protection linting mode is meant
to be invoked after potentially-destructive operations
on Markdown files (e.g. via Remark) to check for links
that have been accidentally disabled.
[boolean] [default: false]
This package can be imported and run directly in source without spawning a child process or calling a CLI. This is useful for, for instance, composing multiple yargs-based CLI tools together or invoking the "link protection" stand-alone checks.
import { configureProgram } from '@projector/plugin-lint';
import { checkForPotentiallyDisabledLinks } from '@projector/plugin-lint/utils';
const { program, parse } = configureProgram();
// `program` is a yargs instance
// `parse` is an async function that will (eventually) call program.parse(...)
await parse(['--help']);
// This package also exposes some of its internal utils for external use
await checkForPotentiallyDisabledLinks();
Further documentation can be found under docs/
.
Specifically, in addition to type checking and static analysis with tsc and ESLint, the following checks are performed:
- ⛔ Errors when the project is not a git repository
- ⛔ Errors when the
package.json
file is missing or unparsable - ⛔ Errors when the
dist
directory or its subdirectories contain.tsbuildinfo
files - ⛔ Errors when
package.json
does not containdescription
,homepage
,repository
,license
,author
,engines
, ortype
fields- When linting a monorepo root, the check for
description
is skipped
- When linting a monorepo root, the check for
- ⛔ Errors when
package.json
does not containname
,version
,keywords
,sideEffects
,exports
,files
, orpublishConfig
fields- When linting a monorepo root, or if the
private
field exists and is set totrue
, this check is skipped
- When linting a monorepo root, or if the
- ⛔ Errors when the same dependency appears under both
dependencies
anddevDependencies
fields inpackage.json
- ⛔ Errors when
package.json
contains thefiles
field but its array is missing"/dist"
,"/LICENSE"
,"/package.json"
, or"/README.md"
elements - ⛔ Errors when
package.json
is missing theexports["./package"]
orexports["./package.json"]
fields, or if they point to files that do not exist - ⛔ Errors when missing
LICENSE
orREADME.md
files - ⛔ ⹋ Errors when an unpublished git commit has "fixup" or "mergeme" in its
subject
- This is evidence that the commit tree needs to be cleaned up before changes are merged upstream!
- ⛔ Errors when any
exports
ortypesVersions
entry points inpackage.json
point to files that do not exist - ⛔ ⹋⹋ Errors when any Markdown file matching the provided
--md-path
glob(s) contain disabled links- This check can be skipped for specific files by setting
config['plugin-lint']['link-protection'].ignore = ['relative/path/or/glob']
inpackage.json
- This check can be skipped for specific files by setting
- ⛔ Errors when depending on a non-pinned pre-release package
version (like
"^x.y.z-alpha.1"
, which should instead be"x.y.z-alpha.1"
)- This is dangerous enough to warrant an error instead of a warning since pre-release versions can differ radically from version to version and should therefore be pinned
- Pinned pre-release package versions will still trigger a "pinned package version" warning (below), as they should
- ⛔ † Errors when a source file contains an import but does not list the
imported package in the
package.json
dependencies
,peerDependencies
, oroptionalDependencies
fields- Also checks for unlisted cross-dependencies when linting a monorepo
- Self-referential imports are excluded from this check
- Checks of type imports additionally consider the
package.json
devDependencies
field - Checks of source files ending in
.test.ts
(or any other supported extension), source files with/test/
or/__test__/
in their path, or source files located at a package root additionally consider thepackage.json
devDependencies
field. These rules can be overwritten by settingconfig['plugin-lint']['imports'].considerDevDeps = ['relative/path/or/glob']
inpackage.json
⚠️ Warns when one or morepackage.json
files exist at a location that is not at the project root or, when linting a monorepo, at a sub-root- Non-root
package.json
files can be used to specify thetype
of.js
files that exist under it in the filesystem tree. Unfortunately, these arbitrarypackage.json
files are not well-supported by Projector tooling yet
- Non-root
⚠️ Warns when missingtsconfig.json
,tsconfig.docs.json
,tsconfig.eslint.json
,tsconfig.lint.json
, ortsconfig.types.json
files- When linting a monorepo root, only
tsconfig.json
,tsconfig.lint.json
, andtsconfig.eslint.json
are checked for existence - When linting a monorepo sub-root, only
tsconfig.docs.json
,tsconfig.lint.json
, andtsconfig.types.json
are checked for existence
- When linting a monorepo root, only
⚠️ Warns whenpackage.json
license
field is not"MIT"
⚠️ Warns whenpackage.json
contains an experimentalversion
(i.e.<1.0.0
)- This INCLUDES the obsoleted "placeholder" version
0.0.0-development
- This INCLUDES the obsoleted "placeholder" version
⚠️ Warns whenpackage.json
contains the outdatedmain
,module
,types
, ortypesVersions
fields- Use
exports
instead
- Use
⚠️ Warns whenpackage.json
contains theengines
field but is missing theengines.node
field, or if it is not set to the maintained and LTS versions of Node.js- For example:
{ "engines": { "node": "^12.22.0 || ^14.19.0 || ^16.13.0 || >=17.4.0" }}
(as of Feb 2022)
- For example:
⚠️ Warns whenpackage.json
is missing thescripts
field⚠️ Warns whenpackage.json
scripts
field contains a script name equal to or starting with any of the following:test-integration-webpack
,test-integration-browser
,prepublishOnly
,postpublish
,repl
,preinstall
,postinstall
,fixup
,check-types
, orpublishGuard
⚠️ Warns whenpackage.json
scripts
field is missing one of the following script names:build
,build-changelog
,build-dist
,build-docs
,build-stats
,clean
,format
,lint
,lint-all
,list-tasks
,prepare
,test
,test-all
,test-integration
,test-repeat-all
,test-repeat-unit
, ortest-unit
- When linting a monorepo root, existence checks for the
build
,build-changelog
,build-dist
,build-docs
, andbuild-stats
script names are skipped- Exceptions for the
build*
script names will most likely be removed in later versions of Projector
- Exceptions for the
- When linting a monorepo sub-root, existence checks for the
prepare
script name is skipped - If a
next.config.js
file exists at the project root, existence checks for the following script names are additionally performed:dev
,start
,test-e2e
- When linting a monorepo root, existence checks for the
⚠️ Warns when depending on a pinned package version (like"x.y.z"
, which should instead be"^x.y.z"
)- Use
package-lock.json
+npm ci
if you want to guarantee the same dependencies are consistently installed
- Use
⚠️ Warns when depending on a dist-tag package version (like"next"
or"latest"
) instead of a proper semver (like"~x.y.z"
)⚠️ Warns whenpackage.json
is missing theconfig['plugin-build'].docs.entry
field, or if it points to a file that does not exist⚠️ ‡ Warns whenREADME.md
does not contain the standard badge topmatter, or when said topmatter is pointing to the wrong package name and/or repo uri- When linting a monorepo, what is considered "standard topmatter" changes depending on the current working directory being within the project root versus a sub-root
⚠️ ‡ Warns when standard links inREADME.md
are missing, or are pointing to the wrong package name and/or repo uri- When linting a monorepo, what is considered "standard links" changes depending on the current working directory being within the project root vs a sub-root
These additional checks are performed if the current project is a monorepo:
- ⛔ Errors when a sub-root
package.json
file is unparsable - ⛔ Errors when a package shares the same
package.json
name
field as another package in the monorepo - ⛔ Errors when an unnamed package shares the same package-id as another unnamed package in the monorepo
These additional checks are performed except when linting a sub-root:
⚠️ Warns when any of the following files are missing:.codecov.yml
.editorconfig
.eslintrc.js
.gitattributes
.gitignore
.prettierignore
.spellcheckignore
babel.config.js
commitlint.config.js
conventional.config.js
jest.config.js
lint-staged.config.js
webpack.config.js
prettier.config.js
CONTRIBUTING.md
SECURITY.md
.github/ISSUE_TEMPLATE/BUG_REPORT.md
.github/ISSUE_TEMPLATE/config.yml
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
.github/CODE_OF_CONDUCT.md
.github/CODEOWNERS
.github/dependabot.yml
.github/pipeline.config.js
.github/PULL_REQUEST_TEMPLATE.md
.github/SUPPORT.md
⚠️ Warns when missing therelease.config.js
file- If the
package.json
private
field exists and is set totrue
, this check is skipped
- If the
⚠️ Warns when missing the.github
,.github/ISSUE_TEMPLATE
,.github/workflows
,.husky
, ortypes
directories⚠️ Warns when the contents ofCONTRIBUTING.md
,SECURITY.md
,.github/SUPPORT.md
,.github/CODE_OF_CONDUCT.md
,.github/PULL_REQUEST_TEMPLATE.md
,.github/ISSUE_TEMPLATE/BUG_REPORT.md
,.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
,.github/ISSUE_TEMPLATE/config.yml
, or.github/dependabot.yml
differ from their latest blueprints⚠️ ‡ Warns whenSECURITY.md
or.github/SUPPORT.md
topmatter is pointing to the wrong package name and/or repo uri⚠️ ‡ Warns when standard links inCONTRIBUTING.md
,SECURITY.md
, or.github/SUPPORT.md
are pointing to the wrong package name and/or repo uri
These additional checks are performed only if linting a monorepo root:
- ⛔ Errors when the
package.json
workspaces
field contains a path that points to a directory without apackage.json
file ⚠️ Warns whenpackage.json
containsdependencies
orversion
fields (0.0.0-monorepo
is allowed)- Since the typical root package of a monorepo is only encountered in
development, any dependencies should always be
devDependencies
- If a
next.config.js
file exists at the project root, this check is skipped
- Since the typical root package of a monorepo is only encountered in
development, any dependencies should always be
⚠️ Warns whenpackage.json
is missing theprivate
field or if it is not set totrue
- Typically, a monorepo's root package is never published
⚠️ Warns whenpackage.json
is missing thename
field- All valid sub-roots defined in the
package.json
workspaces
field are recursively linted
These additional checks are performed only if linting a sub-root:
- ⛔ † Errors when a source file contains an import of a package from the same
monorepo without using the
pkgverse
alias- Using a
pkgverse
alias, which is transformed into a cross-dependency import when built for production, allows Jest tests to run using the direct source code rather than the build artifacts when working in monorepos
- Using a
- ⛔ † Errors when a
pkgverse
alias does not have a matching entry point in thepackage.json
exports
field ⚠️ Warns whenpackage.json
is missing theconfig['plugin-build'].codecov.flag
field⚠️ Warns whenpackage.json
containsdevDependencies
- These should be located in the project root's
package.json
file instead
- These should be located in the project root's
† This check is performed using Babel AST static analysis. Dynamic imports and requires are not checked. Files ending in
.js
(when the package'spackage.json
type
field equals"commonjs"
),.jsx
,.cjs
,.cts
, and.d.*
are excluded from these checks.
‡ This check is performed using mdast-util-from-markdown AST static analysis.
⹋ When in pre-push mode (--pre-push-only
), only these checks are performed. All others are skipped.
⹋⹋ When in link protection mode (--link-protection-only
), only these checks are performed. All others are skipped.
This is a CJS2 package with statically-analyzable exports built by Babel for Node14 and above.
Expand details
That means both CJS2 (via require(...)
) and ESM (via import { ... } from ...
or await import(...)
) source will load this package from the same entry points
when using Node. This has several benefits, the foremost being: less code
shipped/smaller package size, avoiding dual package
hazard entirely, distributables are not
packed/bundled/uglified, and a less complex build process.
Each entry point (i.e. ENTRY
) in package.json
's
exports[ENTRY]
object includes one or more export
conditions. These entries may or may not include: an
exports[ENTRY].types
condition pointing to a type
declarations file for TypeScript and IDEs, an
exports[ENTRY].module
condition pointing to
(usually ESM) source for Webpack/Rollup, an exports[ENTRY].node
condition
pointing to (usually CJS2) source for Node.js require
and import
, an
exports[ENTRY].default
condition pointing to source for browsers and other
environments, and other conditions not enumerated
here. Check the package.json file to see which export
conditions are supported.
Though package.json
includes
{ "type": "commonjs" }
, note that any ESM-only entry points will
be ES module (.mjs
) files. Finally, package.json
also
includes the sideEffects
key, which is false
for
optimal tree shaking.
See LICENSE.
New issues and pull requests are always welcome and greatly appreciated! 🤩 Just as well, you can star 🌟 this project to let me know you found it useful! ✊🏿 Thank you!
See CONTRIBUTING.md and SUPPORT.md for more information.
See the table of contributors.