05d4c9e729
<!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change that you're making: --> ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) Co-authored-by: JJ Kasper <jj@jjsweb.site>
237 lines
8.1 KiB
JavaScript
Executable file
237 lines
8.1 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
import { existsSync } from 'fs'
|
|
import arg from 'next/dist/compiled/arg/index.js'
|
|
import { join } from 'path'
|
|
import chalk from 'next/dist/compiled/chalk'
|
|
|
|
import { cliCommand } from '../lib/commands'
|
|
import { ESLINT_DEFAULT_DIRS } from '../lib/constants'
|
|
import { runLintCheck } from '../lib/eslint/runLintCheck'
|
|
import { printAndExit } from '../server/lib/utils'
|
|
import { Telemetry } from '../telemetry/storage'
|
|
import loadConfig from '../server/config'
|
|
import { PHASE_PRODUCTION_BUILD } from '../shared/lib/constants'
|
|
import { eventLintCheckCompleted } from '../telemetry/events'
|
|
import { CompileError } from '../lib/compile-error'
|
|
import isError from '../lib/is-error'
|
|
import { getProjectDir } from '../lib/get-project-dir'
|
|
|
|
const eslintOptions = (args: arg.Spec, defaultCacheLocation: string) => ({
|
|
overrideConfigFile: args['--config'] || null,
|
|
extensions: args['--ext'] ?? [
|
|
'.js',
|
|
'.mjs',
|
|
'.cjs',
|
|
'.jsx',
|
|
'.ts',
|
|
'.mts',
|
|
'.cts',
|
|
'.tsx',
|
|
],
|
|
resolvePluginsRelativeTo: args['--resolve-plugins-relative-to'] || null,
|
|
rulePaths: args['--rulesdir'] ?? [],
|
|
fix: args['--fix'] ?? false,
|
|
fixTypes: args['--fix-type'] ?? null,
|
|
ignorePath: args['--ignore-path'] || null,
|
|
ignore: !Boolean(args['--no-ignore']),
|
|
allowInlineConfig: !Boolean(args['--no-inline-config']),
|
|
reportUnusedDisableDirectives:
|
|
args['--report-unused-disable-directives'] || null,
|
|
cache: !Boolean(args['--no-cache']),
|
|
cacheLocation: args['--cache-location'] || defaultCacheLocation,
|
|
cacheStrategy: args['--cache-strategy'] || 'metadata',
|
|
errorOnUnmatchedPattern: args['--error-on-unmatched-pattern']
|
|
? Boolean(args['--error-on-unmatched-pattern'])
|
|
: false,
|
|
})
|
|
|
|
const nextLint: cliCommand = async (argv) => {
|
|
const validArgs: arg.Spec = {
|
|
// Types
|
|
'--help': Boolean,
|
|
'--base-dir': String,
|
|
'--dir': [String],
|
|
'--file': [String],
|
|
'--strict': Boolean,
|
|
|
|
// Aliases
|
|
'-h': '--help',
|
|
'-b': '--base-dir',
|
|
'-d': '--dir',
|
|
}
|
|
|
|
const validEslintArgs: arg.Spec = {
|
|
// Types
|
|
'--config': String,
|
|
'--ext': [String],
|
|
'--resolve-plugins-relative-to': String,
|
|
'--rulesdir': [String],
|
|
'--fix': Boolean,
|
|
'--fix-type': [String],
|
|
'--ignore-path': String,
|
|
'--no-ignore': Boolean,
|
|
'--quiet': Boolean,
|
|
'--max-warnings': Number,
|
|
'--no-inline-config': Boolean,
|
|
'--report-unused-disable-directives': String,
|
|
'--cache': Boolean, // Although cache is enabled by default, this dummy flag still exists to not cause any breaking changes
|
|
'--no-cache': Boolean,
|
|
'--cache-location': String,
|
|
'--cache-strategy': String,
|
|
'--error-on-unmatched-pattern': Boolean,
|
|
'--format': String,
|
|
'--output-file': String,
|
|
|
|
// Aliases
|
|
'-c': '--config',
|
|
'-f': '--format',
|
|
'-o': '--output-file',
|
|
}
|
|
|
|
let args: arg.Result<arg.Spec>
|
|
try {
|
|
args = arg({ ...validArgs, ...validEslintArgs }, { argv })
|
|
} catch (error) {
|
|
if (isError(error) && error.code === 'ARG_UNKNOWN_OPTION') {
|
|
return printAndExit(error.message, 1)
|
|
}
|
|
throw error
|
|
}
|
|
if (args['--help']) {
|
|
printAndExit(
|
|
`
|
|
Description
|
|
Run ESLint on every file in specified directories.
|
|
If not configured, ESLint will be set up for the first time.
|
|
|
|
Usage
|
|
$ next lint <baseDir> [options]
|
|
|
|
<baseDir> represents the directory of the Next.js application.
|
|
If no directory is provided, the current directory will be used.
|
|
|
|
Options
|
|
Basic configuration:
|
|
-h, --help List this help
|
|
-d, --dir Array Include directory, or directories, to run ESLint - default: 'pages', 'components', and 'lib'
|
|
--file Array Include file, or files, to run ESLint
|
|
-c, --config path::String Use this configuration file, overriding all other config options
|
|
--ext [String] Specify JavaScript file extensions - default: .js, .mjs, .cjs, .jsx, .ts, .mts, .cts, .tsx
|
|
--resolve-plugins-relative-to path::String A folder where plugins should be resolved from, CWD by default
|
|
|
|
Initial setup:
|
|
--strict Creates an .eslintrc.json file using the Next.js strict configuration (only possible if no .eslintrc.json file is present)
|
|
|
|
Specifying rules:
|
|
--rulesdir [path::String] Use additional rules from this directory
|
|
|
|
Fixing problems:
|
|
--fix Automatically fix problems
|
|
--fix-type Array Specify the types of fixes to apply (problem, suggestion, layout)
|
|
|
|
Ignoring files:
|
|
--ignore-path path::String Specify path of ignore file
|
|
--no-ignore Disable use of ignore files and patterns
|
|
|
|
Handling warnings:
|
|
--quiet Report errors only - default: false
|
|
--max-warnings Int Number of warnings to trigger nonzero exit code - default: -1
|
|
|
|
Output:
|
|
-o, --output-file path::String Specify file to write report to
|
|
-f, --format String Use a specific output format - default: Next.js custom formatter
|
|
|
|
Inline configuration comments:
|
|
--no-inline-config Prevent comments from changing config or rules
|
|
--report-unused-disable-directives Adds reported errors for unused eslint-disable directives ("error" | "warn" | "off")
|
|
|
|
Caching:
|
|
--no-cache Disable caching
|
|
--cache-location path::String Path to the cache file or directory - default: .eslintcache
|
|
--cache-strategy String Strategy to use for detecting changed files in the cache, either metadata or content - default: metadata
|
|
|
|
Miscellaneous:
|
|
--error-on-unmatched-pattern Show errors when any file patterns are unmatched - default: false
|
|
`,
|
|
0
|
|
)
|
|
}
|
|
|
|
const baseDir = getProjectDir(args._[0])
|
|
|
|
// Check if the provided directory exists
|
|
if (!existsSync(baseDir)) {
|
|
printAndExit(`> No such directory exists as the project root: ${baseDir}`)
|
|
}
|
|
|
|
const nextConfig = await loadConfig(PHASE_PRODUCTION_BUILD, baseDir)
|
|
|
|
const files: string[] = args['--file'] ?? []
|
|
const dirs: string[] = args['--dir'] ?? nextConfig.eslint?.dirs
|
|
const filesToLint = [...(dirs ?? []), ...files]
|
|
|
|
const pathsToLint = (
|
|
filesToLint.length ? filesToLint : ESLINT_DEFAULT_DIRS
|
|
).reduce((res: string[], d: string) => {
|
|
const currDir = join(baseDir, d)
|
|
if (!existsSync(currDir)) return res
|
|
res.push(currDir)
|
|
return res
|
|
}, [])
|
|
|
|
const reportErrorsOnly = Boolean(args['--quiet'])
|
|
const maxWarnings = args['--max-warnings'] ?? -1
|
|
const formatter = args['--format'] || null
|
|
const strict = Boolean(args['--strict'])
|
|
const outputFile = args['--output-file'] || null
|
|
|
|
const distDir = join(baseDir, nextConfig.distDir)
|
|
const defaultCacheLocation = join(distDir, 'cache', 'eslint/')
|
|
const hasAppDir = !!nextConfig.experimental.appDir
|
|
|
|
runLintCheck(baseDir, pathsToLint, hasAppDir, {
|
|
lintDuringBuild: false,
|
|
eslintOptions: eslintOptions(args, defaultCacheLocation),
|
|
reportErrorsOnly: reportErrorsOnly,
|
|
maxWarnings,
|
|
formatter,
|
|
outputFile,
|
|
strict,
|
|
})
|
|
.then(async (lintResults) => {
|
|
const lintOutput =
|
|
typeof lintResults === 'string' ? lintResults : lintResults?.output
|
|
|
|
if (typeof lintResults !== 'string' && lintResults?.eventInfo) {
|
|
const telemetry = new Telemetry({
|
|
distDir,
|
|
})
|
|
telemetry.record(
|
|
eventLintCheckCompleted({
|
|
...lintResults.eventInfo,
|
|
buildLint: false,
|
|
})
|
|
)
|
|
await telemetry.flush()
|
|
}
|
|
|
|
if (
|
|
typeof lintResults !== 'string' &&
|
|
lintResults?.isError &&
|
|
lintOutput
|
|
) {
|
|
throw new CompileError(lintOutput)
|
|
}
|
|
|
|
if (lintOutput) {
|
|
printAndExit(lintOutput, 0)
|
|
} else if (lintResults && !lintOutput) {
|
|
printAndExit(chalk.green('✔ No ESLint warnings or errors'), 0)
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
printAndExit(err.message)
|
|
})
|
|
}
|
|
|
|
export { nextLint }
|