Files
speckle-server/utils/eslint-projectwide.mjs

129 lines
3.4 KiB
JavaScript

import path from 'node:path'
import fs from 'node:fs/promises'
import { getESMDirname } from '../eslint.config.mjs'
import * as zx from 'zx'
import events from 'events'
events.setMaxListeners(31)
process.env.FORCE_COLOR = '1'
const rootDir = path.resolve(getESMDirname(import.meta.url), '../')
/**
* Util to run eslint on the entire monorepo
* w/o args: run on everything
* w/ space delimited file names: run on those files
*/
const getFileNames = () => {
// Read argv file names
const fileNames = process.argv.slice(2).filter((f) => !['.', '*', '**/*'].includes(f))
const absoluteFileNames = fileNames.map((f) =>
path.isAbsolute(f) ? f : path.resolve(process.cwd(), f)
)
return absoluteFileNames
}
const resolvePackageContexts = async (absoluteFileNames) => {
const ignoredPackageNames = ['ifc-import-service']
const allPackages = [
...(await fs.readdir(path.join(rootDir, './packages'), { withFileTypes: true }))
.filter((d) => {
return d.isDirectory() && !ignoredPackageNames.includes(d.name)
})
.map((d) => ({
absolutePath: path.join(rootDir, './packages', d.name)
})),
{
absolutePath: rootDir
}
]
if (!absoluteFileNames.length) {
return allPackages.map((p) => ({
...p,
files: ['.']
}))
}
/**
* @type {Map<string, Set<string>>}
*/
const contexts = new Map()
// Group filenames by packages
for (const absoluteFileName of absoluteFileNames) {
const fittingPkgs = allPackages.filter((p) =>
absoluteFileName.startsWith(p.absolutePath)
)
// get pkg w/ longest path to get the most appropriate/fitting one
fittingPkgs.sort((a, b) => b.absolutePath.length - a.absolutePath.length)
const pkg = fittingPkgs[0]
if (!pkg) {
throw new Error(`File ${absoluteFileName} does not belong to any package`)
}
const contextsKey = pkg.absolutePath
if (!contexts.has(contextsKey)) {
contexts.set(contextsKey, new Set())
}
contexts.get(contextsKey).add(absoluteFileName)
}
return [...contexts.entries()].map(([pkgPath, files]) => ({
absolutePath: pkgPath,
files: [...files]
}))
}
const execEslintFromPackageContexts = async (packageContexts) => {
const ac = new AbortController()
/** @type {Array<import('zx').ProcessPromise>} */
const processes = packageContexts.map(async ({ absolutePath, files }) => {
const prefix = `[${path.relative(rootDir, absolutePath)}]`
process.stdout.write(`${prefix} Starting...\n`)
const exec = zx.$({ cwd: absolutePath, signal: ac.signal })
const run =
exec`yarn eslint --cache --max-warnings=0 --no-warn-ignored ${files}`.pipe(
process.stdout
)
try {
await run
process.stdout.write(`${prefix} Finished\n`)
} catch (e) {
process.stdout.write(`${prefix} Error occurred\n`)
throw e
}
})
// Wait for all to finish
const res = await Promise.allSettled(processes)
const failed = res.filter((r) => r.status === 'rejected')
if (failed.length) {
throw failed[0].reason
}
}
const main = async () => {
const fileNames = getFileNames()
const packageContexts = await resolvePackageContexts(fileNames)
try {
await execEslintFromPackageContexts(packageContexts)
} catch (e) {
if (e instanceof zx.ProcessOutput) {
console.error('Error occurred while linting')
process.exit(e.exitCode)
return
}
throw e
}
}
await main()