2021-06-03 14:01:24 +02:00
|
|
|
#!/usr/bin/env node
|
|
|
|
import { existsSync } from 'fs'
|
|
|
|
import arg from 'next/dist/compiled/arg/index.js'
|
2021-09-22 23:29:27 +02:00
|
|
|
import { join } from 'path'
|
2021-12-21 16:13:45 +01:00
|
|
|
import chalk from 'next/dist/compiled/chalk'
|
2021-06-09 00:46:00 +02:00
|
|
|
|
2021-06-03 14:01:24 +02:00
|
|
|
import { cliCommand } from '../bin/next'
|
2021-06-10 11:02:50 +02:00
|
|
|
import { ESLINT_DEFAULT_DIRS } from '../lib/constants'
|
2021-06-03 14:01:24 +02:00
|
|
|
import { runLintCheck } from '../lib/eslint/runLintCheck'
|
|
|
|
import { printAndExit } from '../server/lib/utils'
|
2021-06-15 03:31:40 +02:00
|
|
|
import { Telemetry } from '../telemetry/storage'
|
2021-06-30 13:44:40 +02:00
|
|
|
import loadConfig from '../server/config'
|
2021-06-30 11:43:31 +02:00
|
|
|
import { PHASE_PRODUCTION_BUILD } from '../shared/lib/constants'
|
2021-06-15 03:31:40 +02:00
|
|
|
import { eventLintCheckCompleted } from '../telemetry/events'
|
|
|
|
import { CompileError } from '../lib/compile-error'
|
2021-09-16 18:06:57 +02:00
|
|
|
import isError from '../lib/is-error'
|
2021-09-22 23:29:27 +02:00
|
|
|
import { getProjectDir } from '../lib/get-project-dir'
|
2021-06-03 14:01:24 +02:00
|
|
|
|
2021-08-23 19:56:21 +02:00
|
|
|
const eslintOptions = (args: arg.Spec, defaultCacheLocation: string) => ({
|
2021-06-10 11:02:50 +02:00
|
|
|
overrideConfigFile: args['--config'] || null,
|
|
|
|
extensions: args['--ext'] ?? ['.js', '.jsx', '.ts', '.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,
|
2021-08-23 19:56:21 +02:00
|
|
|
cache: !Boolean(args['--no-cache']),
|
|
|
|
cacheLocation: args['--cache-location'] || defaultCacheLocation,
|
2021-11-08 18:17:42 +01:00
|
|
|
cacheStrategy: args['--cache-strategy'] || 'metadata',
|
2021-07-16 20:19:08 +02:00
|
|
|
errorOnUnmatchedPattern: args['--error-on-unmatched-pattern']
|
|
|
|
? Boolean(args['--error-on-unmatched-pattern'])
|
|
|
|
: false,
|
2021-06-10 11:02:50 +02:00
|
|
|
})
|
|
|
|
|
2021-07-25 06:11:29 +02:00
|
|
|
const nextLint: cliCommand = async (argv) => {
|
2021-06-03 14:01:24 +02:00
|
|
|
const validArgs: arg.Spec = {
|
|
|
|
// Types
|
|
|
|
'--help': Boolean,
|
2021-06-10 11:02:50 +02:00
|
|
|
'--base-dir': String,
|
2021-06-03 14:01:24 +02:00
|
|
|
'--dir': [String],
|
2021-09-06 13:05:40 +02:00
|
|
|
'--file': [String],
|
2021-08-04 23:53:15 +02:00
|
|
|
'--strict': Boolean,
|
2021-06-03 14:01:24 +02:00
|
|
|
|
|
|
|
// Aliases
|
|
|
|
'-h': '--help',
|
2021-06-10 11:02:50 +02:00
|
|
|
'-b': '--base-dir',
|
2021-06-03 14:01:24 +02:00
|
|
|
'-d': '--dir',
|
|
|
|
}
|
|
|
|
|
2021-06-10 11:02:50 +02:00
|
|
|
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,
|
2021-06-18 15:17:53 +02:00
|
|
|
'--quiet': Boolean,
|
2021-06-29 12:12:23 +02:00
|
|
|
'--max-warnings': Number,
|
2021-06-10 11:02:50 +02:00
|
|
|
'--no-inline-config': Boolean,
|
|
|
|
'--report-unused-disable-directives': String,
|
2021-08-23 19:56:21 +02:00
|
|
|
'--cache': Boolean, // Although cache is enabled by default, this dummy flag still exists to not cause any breaking changes
|
|
|
|
'--no-cache': Boolean,
|
2021-06-10 11:02:50 +02:00
|
|
|
'--cache-location': String,
|
2021-11-08 18:17:42 +01:00
|
|
|
'--cache-strategy': String,
|
2021-07-16 20:19:08 +02:00
|
|
|
'--error-on-unmatched-pattern': Boolean,
|
2021-07-24 03:35:56 +02:00
|
|
|
'--format': String,
|
2021-06-10 11:02:50 +02:00
|
|
|
|
|
|
|
// Aliases
|
|
|
|
'-c': '--config',
|
2021-07-24 03:35:56 +02:00
|
|
|
'-f': '--format',
|
2021-06-10 11:02:50 +02:00
|
|
|
}
|
|
|
|
|
2021-06-03 14:01:24 +02:00
|
|
|
let args: arg.Result<arg.Spec>
|
|
|
|
try {
|
2021-06-10 11:02:50 +02:00
|
|
|
args = arg({ ...validArgs, ...validEslintArgs }, { argv })
|
2021-06-03 14:01:24 +02:00
|
|
|
} catch (error) {
|
2021-09-16 18:06:57 +02:00
|
|
|
if (isError(error) && error.code === 'ARG_UNKNOWN_OPTION') {
|
2021-06-03 14:01:24 +02:00
|
|
|
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
|
2021-09-06 13:05:40 +02:00
|
|
|
$ next lint <baseDir> [options]
|
|
|
|
|
2021-06-03 14:01:24 +02:00
|
|
|
<baseDir> represents the directory of the Next.js application.
|
|
|
|
If no directory is provided, the current directory will be used.
|
|
|
|
|
|
|
|
Options
|
2021-06-10 11:02:50 +02:00
|
|
|
Basic configuration:
|
|
|
|
-h, --help List this help
|
2021-09-06 13:05:40 +02:00
|
|
|
-d, --dir Array Include directory, or directories, to run ESLint - default: 'pages', 'components', and 'lib'
|
|
|
|
--file Array Include file, or files, to run ESLint
|
2021-06-10 11:02:50 +02:00
|
|
|
-c, --config path::String Use this configuration file, overriding all other config options
|
|
|
|
--ext [String] Specify JavaScript file extensions - default: .js, .jsx, .ts, .tsx
|
|
|
|
--resolve-plugins-relative-to path::String A folder where plugins should be resolved from, CWD by default
|
|
|
|
|
2021-08-04 23:53:15 +02:00
|
|
|
Initial setup:
|
|
|
|
--strict Creates an .eslintrc.json file using the Next.js strict configuration (only possible if no .eslintrc.json file is present)
|
|
|
|
|
2021-06-10 11:02:50 +02:00
|
|
|
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
|
|
|
|
|
2021-06-18 15:17:53 +02:00
|
|
|
Handling warnings:
|
|
|
|
--quiet Report errors only - default: false
|
2021-06-29 12:12:23 +02:00
|
|
|
--max-warnings Int Number of warnings to trigger nonzero exit code - default: -1
|
2021-07-24 03:35:56 +02:00
|
|
|
|
|
|
|
Output:
|
|
|
|
-f, --format String Use a specific output format - default: Next.js custom formatter
|
2021-06-18 15:17:53 +02:00
|
|
|
|
2021-06-10 11:02:50 +02:00
|
|
|
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:
|
2021-08-23 19:56:21 +02:00
|
|
|
--no-cache Disable caching
|
2021-06-10 11:02:50 +02:00
|
|
|
--cache-location path::String Path to the cache file or directory - default: .eslintcache
|
2021-11-08 18:17:42 +01:00
|
|
|
--cache-strategy String Strategy to use for detecting changed files in the cache, either metadata or content - default: metadata
|
2021-06-10 11:02:50 +02:00
|
|
|
|
|
|
|
Miscellaneous:
|
2021-07-16 20:19:08 +02:00
|
|
|
--error-on-unmatched-pattern Show errors when any file patterns are unmatched - default: false
|
2021-06-10 11:02:50 +02:00
|
|
|
`,
|
2021-06-03 14:01:24 +02:00
|
|
|
0
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-09-22 23:29:27 +02:00
|
|
|
const baseDir = getProjectDir(args._[0])
|
2021-06-03 14:01:24 +02:00
|
|
|
|
|
|
|
// Check if the provided directory exists
|
|
|
|
if (!existsSync(baseDir)) {
|
|
|
|
printAndExit(`> No such directory exists as the project root: ${baseDir}`)
|
|
|
|
}
|
|
|
|
|
2021-08-23 19:56:21 +02:00
|
|
|
const nextConfig = await loadConfig(PHASE_PRODUCTION_BUILD, baseDir)
|
2021-07-25 06:11:29 +02:00
|
|
|
|
2021-09-06 13:05:40 +02:00
|
|
|
const files: string[] = args['--file'] ?? []
|
2021-08-23 19:56:21 +02:00
|
|
|
const dirs: string[] = args['--dir'] ?? nextConfig.eslint?.dirs
|
2021-09-06 13:05:40 +02:00
|
|
|
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
|
|
|
|
}, [])
|
2021-06-18 15:17:53 +02:00
|
|
|
|
|
|
|
const reportErrorsOnly = Boolean(args['--quiet'])
|
2021-06-29 12:12:23 +02:00
|
|
|
const maxWarnings = args['--max-warnings'] ?? -1
|
2021-07-24 03:35:56 +02:00
|
|
|
const formatter = args['--format'] || null
|
2021-08-04 23:53:15 +02:00
|
|
|
const strict = Boolean(args['--strict'])
|
2021-06-29 12:12:23 +02:00
|
|
|
|
2021-08-23 19:56:21 +02:00
|
|
|
const distDir = join(baseDir, nextConfig.distDir)
|
|
|
|
const defaultCacheLocation = join(distDir, 'cache', 'eslint/')
|
|
|
|
|
2021-06-29 12:12:23 +02:00
|
|
|
runLintCheck(
|
|
|
|
baseDir,
|
2021-09-06 13:05:40 +02:00
|
|
|
pathsToLint,
|
2021-06-29 12:12:23 +02:00
|
|
|
false,
|
2021-08-23 19:56:21 +02:00
|
|
|
eslintOptions(args, defaultCacheLocation),
|
2021-06-29 12:12:23 +02:00
|
|
|
reportErrorsOnly,
|
2021-07-24 03:35:56 +02:00
|
|
|
maxWarnings,
|
2021-08-04 23:53:15 +02:00
|
|
|
formatter,
|
|
|
|
strict
|
2021-06-29 12:12:23 +02:00
|
|
|
)
|
2021-06-15 03:31:40 +02:00
|
|
|
.then(async (lintResults) => {
|
|
|
|
const lintOutput =
|
|
|
|
typeof lintResults === 'string' ? lintResults : lintResults?.output
|
|
|
|
|
|
|
|
if (typeof lintResults !== 'string' && lintResults?.eventInfo) {
|
|
|
|
const telemetry = new Telemetry({
|
2021-08-23 19:56:21 +02:00
|
|
|
distDir,
|
2021-06-15 03:31:40 +02:00
|
|
|
})
|
|
|
|
telemetry.record(
|
|
|
|
eventLintCheckCompleted({
|
|
|
|
...lintResults.eventInfo,
|
|
|
|
buildLint: false,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
await telemetry.flush()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
typeof lintResults !== 'string' &&
|
|
|
|
lintResults?.isError &&
|
|
|
|
lintOutput
|
|
|
|
) {
|
|
|
|
throw new CompileError(lintOutput)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lintOutput) {
|
2021-08-19 19:16:59 +02:00
|
|
|
printAndExit(lintOutput, 0)
|
2021-08-04 23:53:15 +02:00
|
|
|
} else if (lintResults && !lintOutput) {
|
2021-08-19 19:16:59 +02:00
|
|
|
printAndExit(chalk.green('✔ No ESLint warnings or errors'), 0)
|
2021-06-09 00:46:00 +02:00
|
|
|
}
|
2021-06-03 14:01:24 +02:00
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
printAndExit(err.message)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
export { nextLint }
|