Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve how configures are resolved #26

Merged
merged 8 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion app/server/api/payload.json.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import process from 'node:process'
import { createWsServer } from '~~/src/ws'

export default lazyEventHandler(async () => {
const ws = await createWsServer()
const ws = await createWsServer({
cwd: process.cwd(),
})

return defineEventHandler(async () => {
return await ws.getData()
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"connect": "^3.7.0",
"esbuild": "^0.20.2",
"fast-glob": "^3.3.2",
"find-up": "^7.0.0",
"get-port-please": "^3.1.2",
"minimatch": "^9.0.4",
"open": "^10.1.0",
Expand Down
10 changes: 3 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 24 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,31 @@ const cli = cac()

cli
.command('build', 'Build inspector with current config file for static hosting')
.option('--config <configFile>', 'Config file path', { default: process.env.ESLINT_CONFIG })
.option('--config <configFile>', 'Config file path')
.option('--basePath <basePath>', 'Base directory for globs to resolve. Default to directory of config file if not provided')
.option('--out-dir <dir>', 'Output directory', { default: '.eslint-config-inspector' })
.action(async (options) => {
console.log('Building static ESLint config inspector...')

if (process.env.ESLINT_CONFIG)
options.config ||= process.env.ESLINT_CONFIG

const cwd = process.cwd()
const outDir = resolve(cwd, options.outDir)
const configs = await readConfig(cwd, options.config || process.env.ESLINT_CONFIG)
const configs = await readConfig({
cwd,
userConfigPath: options.config,
userBasePath: options.basePath,
})

if (existsSync(outDir))
await fs.rm(outDir, { recursive: true })
await fs.mkdir(outDir, { recursive: true })
await fs.cp(distDir, outDir, { recursive: true })
await fs.mkdir(resolve(outDir, 'api'), { recursive: true })

configs.payload.meta.configPath = ''
configs.payload.meta.rootPath = ''
await fs.writeFile(resolve(outDir, 'api/payload.json'), JSON.stringify(configs.payload, null, 2), 'utf-8')

console.log(`Built to ${relative(cwd, outDir)}`)
Expand All @@ -35,19 +46,26 @@ cli

cli
.command('', 'Start dev inspector')
.option('--config <configFile>', 'Config file path', { default: process.env.ESLINT_CONFIG })
.option('--config <configFile>', 'Config file path')
.option('--basePath <basePath>', 'Base directory for globs to resolve. Default to directory of config file if not provided')
.option('--host <host>', 'Host', { default: process.env.HOST || '127.0.0.1' })
.option('--port <port>', 'Port', { default: process.env.PORT || 7777 })
.option('--open', 'Open browser', { default: true })
.action(async (options) => {
const host = options.host
const port = await getPort({ port: options.port })
if (options.config)
process.env.ESLINT_CONFIG = options.config

if (process.env.ESLINT_CONFIG)
options.config ||= process.env.ESLINT_CONFIG

console.log(`Starting ESLint config inspector at http://${host}:${port}`)

const server = await createHostServer()
const cwd = process.cwd()
const server = await createHostServer({
cwd,
userConfigPath: options.config,
userBasePath: options.basePath,
})

server.listen(port, host)

Expand Down
47 changes: 41 additions & 6 deletions src/configs.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import fs from 'node:fs'
import { dirname, resolve } from 'node:path'
import process from 'node:process'
import { resolve } from 'node:path'
import { bundleRequire } from 'bundle-require'
import type { Linter } from 'eslint'
import fg from 'fast-glob'
import { findUp } from 'find-up'
import type { Payload, RuleInfo } from '../types'

const configFilenames = [
Expand All @@ -15,14 +15,48 @@ const configFilenames = [
'eslint.config.cts',
]

export interface ReadConfigOptions {
cwd: string
userConfigPath?: string
userBasePath?: string
/**
* Change current working directory to rootPath
* @default true
*/
chdir?: boolean
}

export async function readConfig(
cwd: string,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some JSDoc for this function so it's easier to understand what exactly is being passed in?

configPathOverride = process.env.ESLINT_CONFIG,
{
cwd,
userConfigPath,
userBasePath,
chdir = true,
}: ReadConfigOptions,
): Promise<{ payload: Payload, dependencies: string[] }> {
const configPath = resolve(cwd, configPathOverride || configFilenames.find(i => fs.existsSync(resolve(cwd, i))) || configFilenames[0])
if (userBasePath)
userBasePath = resolve(cwd, userBasePath)

const configPath = userConfigPath
? resolve(cwd, userConfigPath)
: await findUp(configFilenames, { cwd: userBasePath || cwd })

if (!configPath)
throw new Error('Cannot find ESLint config file')

const rootPath = userBasePath || (
userConfigPath
? cwd // When user explicit provide config path, use current working directory as root
: dirname(configPath) // Otherwise, use config file's directory as root
)

if (chdir && rootPath !== process.cwd())
process.chdir(rootPath)

console.log('Reading ESLint configs from', configPath)
const { mod, dependencies } = await bundleRequire({
filepath: configPath,
cwd: rootPath,
})

const rawConfigs = await (mod.default ?? mod) as Linter.FlatConfig[]
Expand Down Expand Up @@ -71,7 +105,7 @@ export async function readConfig(
const files = await fg(
configs.flatMap(i => i.files ?? []).filter(i => typeof i === 'string') as string[],
{
cwd,
cwd: rootPath,
onlyFiles: true,
ignore: [
'**/node_modules/**',
Expand All @@ -87,6 +121,7 @@ export async function readConfig(
files,
meta: {
lastUpdate: Date.now(),
rootPath,
configPath,
},
}
Expand Down
6 changes: 3 additions & 3 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { createServer } from 'node:http'
import connect from 'connect'
import sirv from 'sirv'
import { createWsServer } from './ws'
import { type CreateWsServerOptions, createWsServer } from './ws'
import { distDir } from './dirs'

export async function createHostServer() {
export async function createHostServer(options: CreateWsServerOptions) {
const app = connect()

const ws = await createWsServer()
const ws = await createWsServer(options)

app.use('/api/payload.json', async (_req, res) => {
res.setHeader('Content-Type', 'application/json')
Expand Down
14 changes: 7 additions & 7 deletions src/ws.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import process from 'node:process'
import { relative } from 'node:path'
import chokidar from 'chokidar'
import type { WebSocket } from 'ws'
import { WebSocketServer } from 'ws'
import { getPort } from 'get-port-please'
import { readConfig } from './configs'
import { type ReadConfigOptions, readConfig } from './configs'
import type { Payload } from '~~/types'

const readErrorWarning = `Failed to load \`eslint.config.js\`.
Note that \`@eslint/config-inspector\` only works with the flat config format:
https://eslint.org/docs/latest/use/configure/configuration-files-new`

export async function createWsServer() {
const cwd = process.cwd()
export interface CreateWsServerOptions extends ReadConfigOptions {}

export async function createWsServer(options: CreateWsServerOptions) {
let payload: Payload | undefined
const port = await getPort({ port: 7811, random: true })
const wss = new WebSocketServer({
Expand All @@ -28,7 +28,7 @@ export async function createWsServer() {

const watcher = chokidar.watch([], {
ignoreInitial: true,
cwd: process.cwd(),
cwd: options.cwd,
disableGlobbing: true,
})

Expand All @@ -46,11 +46,11 @@ export async function createWsServer() {
async function getData() {
try {
if (!payload) {
return await readConfig(cwd)
return await readConfig(options)
.then((res) => {
const _payload = payload = res.payload
_payload.meta.wsPort = port
console.log(`Read ESLint config from \`${relative(cwd, _payload.meta.configPath)}\` with`, _payload.configs.length, 'configs and', Object.keys(_payload.rules).length, 'rules')
console.log(`Read ESLint config from \`${relative(options.cwd, _payload.meta.configPath)}\` with`, _payload.configs.length, 'configs and', Object.keys(_payload.rules).length, 'rules')
watcher.add(res.dependencies)
return payload
})
Expand Down
1 change: 1 addition & 0 deletions types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface ResolvedPayload extends Payload {
export interface PayloadMeta {
wsPort?: number
lastUpdate: number
rootPath: string
configPath: string
}

Expand Down
Loading