diff --git a/main.ts b/main.ts index 10b09e4..6f5331f 100755 --- a/main.ts +++ b/main.ts @@ -15,6 +15,7 @@ import { extractTagsWithOperations } from './src/generators/clean-arch.generator'; import { generateReport } from './src/generators/report.generator'; +import { lintGeneratedFiles } from './src/generators/lint.generator'; import { findEnvironmentFile, parseApiKeys } from './src/utils/environment-finder'; import { askApiKeysForTags, askSelectionFilter } from './src/utils/prompt'; import type { SelectionFilter } from './src/types'; @@ -37,6 +38,7 @@ program .option('-t, --templates ', 'Custom templates directory', path.join(__dirname, 'templates')) .option('--skip-install', 'Skip dependency installation') .option('--dry-run', 'Simulate without generating files') + .option('--skip-lint', 'Skip post-generation linting and formatting') .option('-s, --select-endpoints', 'Interactively select which tags and endpoints to generate') .parse(process.argv); @@ -132,6 +134,10 @@ async function main(): Promise { ); cleanup(tempDir); + if (!options.skipLint) { + lintGeneratedFiles(options.output); + } + const report = generateReport(options.output, analysis); console.log('\n' + '='.repeat(60)); diff --git a/src/generators/lint.generator.ts b/src/generators/lint.generator.ts new file mode 100644 index 0000000..2ac81f1 --- /dev/null +++ b/src/generators/lint.generator.ts @@ -0,0 +1,106 @@ +import fs from 'fs-extra'; +import path from 'path'; +import { spawnSync } from 'child_process'; +import { logStep, logSuccess, logWarning, logInfo } from '../utils/logger'; + +/** + * Walks up the directory tree from `startDir` to find the nearest + * directory containing a `package.json` (i.e. the project root). + * Returns `null` if none is found before reaching the filesystem root. + */ +function findProjectRoot(startDir: string): string | null { + let current = path.resolve(startDir); + while (true) { + if (fs.existsSync(path.join(current, 'package.json'))) return current; + const parent = path.dirname(current); + if (parent === current) return null; + current = parent; + } +} + +/** + * Collects all `.ts` files recursively inside a directory. + */ +function collectTsFiles(dir: string): string[] { + if (!fs.existsSync(dir)) return []; + const results: string[] = []; + fs.readdirSync(dir).forEach((entry) => { + const full = path.join(dir, entry); + if (fs.statSync(full).isDirectory()) { + results.push(...collectTsFiles(full)); + } else if (entry.endsWith('.ts')) { + results.push(full); + } + }); + return results; +} + +/** + * Runs a command synchronously and returns whether it succeeded. + * Prints stdout/stderr to the console only on failure. + */ +function run(cmd: string, args: string[], cwd: string): boolean { + const result = spawnSync(cmd, args, { cwd, encoding: 'utf8', shell: true }); + if (result.status !== 0) { + if (result.stderr) process.stderr.write(result.stderr); + if (result.stdout) process.stdout.write(result.stdout); + return false; + } + return true; +} + +/** + * Runs Prettier and ESLint (--fix) on all generated `.ts` files inside `outputDir`. + * Both tools are looked up via `npx` in the nearest project root so that the + * target Angular project's own configuration and plugins are used. + * + * - Prettier: always attempted; logs a warning if not found. + * - ESLint: optional; silently skipped if no config is found in the project root. + */ +export function lintGeneratedFiles(outputDir: string): void { + logStep('Linting generated files...'); + + const projectRoot = findProjectRoot(outputDir); + if (!projectRoot) { + logWarning('Could not locate a project root (package.json). Skipping lint.'); + return; + } + logInfo(` Project root: ${projectRoot}`); + + const files = collectTsFiles(outputDir); + if (files.length === 0) { + logWarning('No TypeScript files found in output directory. Skipping lint.'); + return; + } + + const relativePaths = files.map((f) => path.relative(projectRoot, f)); + + // --- Prettier --- + const prettierOk = run('npx', ['prettier', '--write', ...relativePaths], projectRoot); + if (prettierOk) { + logSuccess(`Prettier formatted ${files.length} files`); + } else { + logWarning('Prettier not available or encountered errors. Skipping formatting.'); + } + + // --- ESLint (only if a config exists in the project root) --- + const hasEslintConfig = + fs.existsSync(path.join(projectRoot, 'eslint.config.js')) || + fs.existsSync(path.join(projectRoot, 'eslint.config.mjs')) || + fs.existsSync(path.join(projectRoot, '.eslintrc.js')) || + fs.existsSync(path.join(projectRoot, '.eslintrc.json')) || + fs.existsSync(path.join(projectRoot, '.eslintrc.yml')) || + fs.existsSync(path.join(projectRoot, '.eslintrc.yaml')); + + if (!hasEslintConfig) { + logWarning('No ESLint config found in project root. Skipping ESLint fix.'); + return; + } + + const eslintOk = run('npx', ['eslint', '--fix', ...relativePaths], projectRoot); + if (eslintOk) { + logSuccess(`ESLint fixed ${files.length} files`); + } else { + logWarning('ESLint reported errors that could not be auto-fixed. Review the output above.'); + } +} diff --git a/src/types/cli.types.ts b/src/types/cli.types.ts index bea1609..bc2605a 100644 --- a/src/types/cli.types.ts +++ b/src/types/cli.types.ts @@ -9,4 +9,5 @@ export interface CliOptions { skipInstall?: boolean; dryRun?: boolean; selectEndpoints?: boolean; + skipLint?: boolean; }