#!/usr/bin/env tsx import { promises as fs } from 'node:fs'; import { resolve, relative } from 'node:path'; import { isBuiltin } from 'node:module'; import esbuild from 'esbuild'; import type { Plugin } from 'esbuild'; import { memoize } from 'lodash'; import { gray, green } from 'picocolors'; import { minify_sync } from 'terser'; import { dependencies } from '../dist/package.json'; import { buildLocalRules } from './build-local-rules'; import { dts } from './dts'; import { babelPlugin } from './modifier'; const ENV = (process.env.NODE_ENV ??= 'production'); const PROD = ENV === 'production'; declare global { interface Array { filter( predicate: BooleanConstructor, ): Exclude[]; } } const log = memoize(console.log); const plugins: Plugin[] = [ babelPlugin, { name: 'alias', setup(build) { build.onResolve({ filter: /^jsx-ast-utils$/ }, () => ({ path: resolve('./packages/jsx-ast-utils/src/index.js'), })); build.onResolve({ filter: /^jsx-ast-utils\/.+$/ }, ({ path }) => ({ path: resolve('./packages/jsx-ast-utils/', path.slice('jsx-ast-utils/'.length)) + '.js', })); }, }, ]; if (process.env.DEBUG) { plugins.push({ name: 'deps-check', setup(build) { const declared = new Set(Object.keys(dependencies)); build.onResolve({ filter: /^.*$/ }, ({ path, importer }) => { if ( !path.startsWith('./') && !path.startsWith('../') && !isBuiltin(path) && path !== 'eslint' && !path.startsWith('eslint/') && !path.startsWith('eslint-module-utils/') && !declared.has(path) ) { log(green(path), gray('from'), './' + relative(process.cwd(), importer)); } return null; }); }, }); } async function bundle( entry: string, outfile = entry .replace('./packages/', './dist/') .replace('src/', '') .replace('.ts', '.js'), options?: esbuild.BuildOptions & { treeShaking?: boolean }, ) { const output = await esbuild.build({ entryPoints: [entry], outfile, bundle: true, minify: PROD, platform: 'node', packages: 'external', sourcemap: 'linked', plugins, define: {}, alias: {}, external: ['find-cache-dir'], banner: { js: '/* eslint-disable */', }, ...options, }); if (options?.treeShaking) { const [text, setText] = await useText(outfile); const minified = minify_sync(text, { module: true, compress: { conditionals: true, dead_code: true, defaults: false, evaluate: true, passes: 3, pure_new: true, side_effects: true, unused: true, }, mangle: false, format: { comments: true, }, }); await setText(minified.code!); } return output; } async function editPackageJson() { const [state, setState] = await useText('./dist/package.json'); const distPackageJson = JSON.parse(state); const overrideList = await fs.readdir('dist/overrides'); const npmOverrides = Object.fromEntries( overrideList.map(name => [name, `file:./overrides/${name}`]), ); Object.assign(distPackageJson, { overrides: npmOverrides, resolutions: Object.fromEntries( overrideList.map(name => [`**/${name}`, `file:./overrides/${name}`]), ), pnpm: { overrides: npmOverrides }, }); await setState(JSON.stringify(distPackageJson, null, 2) + '\n'); } async function useText(path: string) { const state = await fs.readFile(path, 'utf-8'); const setState = (text: string) => fs.writeFile(path, text); return [state, setState] as const; } function bundleType(source: string, output: string) { return dts({ source, dist: output, project: './tsconfig.build.json', }); } async function main() { console.log('Building local rules…'); await buildLocalRules(); console.log('Building type definitions…'); bundleType('./src/index.ts', './dist/index.d.ts'); bundleType('./src/prettier.ts', './dist/prettier.d.ts'); bundleType('./src/types.ts', './dist/types.d.ts'); const unminify = { minify: false }; const treeShake = { treeShaking: true, minify: false }; console.log('Building packages…'); await Promise.all([ bundle('./packages/eslint-plugin-jsx-a11y/src/index.js'), bundle('./packages/eslint-plugin-react-hooks/index.ts'), bundle('./packages/eslint-plugin-n/lib/index.js', './dist/eslint-plugin-n/index.js'), bundle('./packages/eslint-import-resolver-typescript/src/index.ts'), bundle('./src/rules/index.ts', './dist/eslint-plugin-rules/index.js'), bundle('./src/local/index.ts', './dist/eslint-plugin-local/index.js'), bundle('./src/index.ts', './dist/index.js', unminify), bundle('./src/types.ts', './dist/types.js', unminify), bundle('./src/prettier.ts', './dist/prettier.js', unminify), bundle('./src/install.ts', './dist/install.js', treeShake), editPackageJson(), ]); console.log('Removing redirect…'); const [distIndex, setDistIndex] = await useText('./dist/index.js'); await setDistIndex(distIndex.replace(/import.*redirect.*;/g, '')); } void main();