import os from 'os'; import type { BuildContext } from '@genesislcap/build-kit'; import { run, resolveBin } from '@genesislcap/build-kit'; import consola from 'consola'; /** ESLint file glob; Oxlint scope is aligned via `OXLINT_CLI_IGNORE_PATTERNS` (extensions + `.genx`). */ const ESLINT_TS_JS_GLOB = '"./**/*.{ts,js,tsx,jsx}"'; /** * Oxlint only accepts directory/file PATHs, not ESLint-style brace globs. Lint `.` and ignore other * extensions Oxlint supports so scope matches `ESLINT_TS_JS_GLOB` (plain `.ts` / `.js` only). */ /** CLI flags: Oxlint does not reliably apply `ignorePatterns` from extended configs for dot-directories like `.genx`. */ const OXLINT_CLI_IGNORE_PATTERNS = [ '**/.genx/**', '**/*.mjs', '**/*.cjs', '**/*.mts', '**/*.cts', '**/*.vue', '**/*.svelte', '**/*.astro', ] .map((pattern) => ` --ignore-pattern="${pattern}"`) .join(''); /** * TODO: https://github.com/oxc-project/oxc/issues/1117 - Swap `ci` pipeline to `ox` once import/no-extraneous-dependencies lands in oxlint. */ async function runOxlintStep(cwd: string, oxlintFixArg: string): Promise { consola.info('Running Oxlint...'); const oxlint = await resolveBin('oxlint'); const oxlintCmd = `${oxlint} .${OXLINT_CLI_IGNORE_PATTERNS}${oxlintFixArg}`; consola.info(`Executing: ${oxlintCmd}`); run(cwd, oxlintCmd); consola.success('Oxlint completed'); } async function runEslintStep( cwd: string, fixArg: string, profile: boolean | undefined, concurrency: string | number | undefined, disablePrettierNoise = false, ): Promise { consola.info('Running ESLint...'); if (profile) { process.env.TIMING = '1'; } const eslint = await resolveBin('eslint'); let concurrencyArg = ''; if (concurrency) { concurrencyArg = ` --concurrency ${concurrency}`; } else if (!process.env.CI) { const defaultConcurrency = Math.max(1, Math.floor(os.cpus().length / 2)); concurrencyArg = ` --concurrency ${defaultConcurrency}`; } const formattingNoiseArg = disablePrettierNoise ? [ ' --rule "prettier/prettier: off"', ' --rule "max-len: off"', ' --rule "eol-last: off"', ' --rule "jsx-quotes: off"', ' --rule "spaced-comment: off"', ].join('') : ''; const command = `${eslint} ${ESLINT_TS_JS_GLOB} ${fixArg}${concurrencyArg}${formattingNoiseArg}`; consola.info(`Executing: ${command}`); run(cwd, command); consola.success('ESLint completed'); } async function runOxfmtStep(cwd: string, fix: boolean | undefined): Promise { consola.info('Running Oxfmt...'); const oxfmt = await resolveBin('oxfmt'); const oxfmtCmd = fix ? `${oxfmt} .` : `${oxfmt} --check .`; consola.info(`Executing: ${oxfmtCmd}`); run(cwd, oxfmtCmd); consola.success('Oxfmt completed'); } async function runStylelintStep(cwd: string, fixArg: string): Promise { consola.info('Running Stylelint...'); const stylelint = await resolveBin('stylelint'); run(cwd, `${stylelint} "./**/*.styles.ts" ${fixArg} --allow-empty-input`); consola.success('Stylelint completed'); } export default async (ctx: BuildContext) => { const { dirs: { cwd }, cli: { isLint, options: { profile, linter, fix, concurrency }, }, } = ctx; if (isLint) { const fixArg = fix ? ' --fix' : ''; const oxlintFixArg = fix ? ' --fix' : ''; // ox pipeline: Oxlint → Oxfmt → Stylelint. if (linter === 'ox') { await runOxlintStep(cwd, oxlintFixArg); await runOxfmtStep(cwd, fix); await runStylelintStep(cwd, fixArg); return; } // CI pipeline: Oxlint → ESLint → Oxfmt → Stylelint. // Keeps ESLint for rules not yet in Oxlint (e.g. import/no-extraneous-dependencies). // Once Oxlint covers all CI rules, swap to `linter === 'ox'`. if (linter === 'ci') { await runOxlintStep(cwd, oxlintFixArg); await runEslintStep(cwd, fixArg, profile, concurrency, true); await runOxfmtStep(cwd, fix); await runStylelintStep(cwd, fixArg); return; } // Default and `-l all`: ESLint + Prettier + Stylelint (legacy, backward-compatible). // Use `genx lint -l ox` for the new Oxlint/Oxfmt stack. const runAll = !linter || linter === 'all'; const runOxfmt = linter === 'oxfmt'; const runOxlint = linter === 'oxlint'; const runEslint = runAll || linter === 'eslint'; const runStylelint = runAll || linter === 'stylelint'; if (runOxlint) { await runOxlintStep(cwd, oxlintFixArg); } if (runOxfmt) { await runOxfmtStep(cwd, fix); } if (runEslint) { await runEslintStep(cwd, fixArg, profile, concurrency, false); } if (runStylelint) { await runStylelintStep(cwd, fixArg); } } else { throw new Error(`Unrecognized command: ${JSON.stringify(ctx.cli.options)}`); } };