feat: add linting option and implement linting for generated TypeScript files
This commit is contained in:
6
main.ts
6
main.ts
@@ -15,6 +15,7 @@ import {
|
|||||||
extractTagsWithOperations
|
extractTagsWithOperations
|
||||||
} from './src/generators/clean-arch.generator';
|
} from './src/generators/clean-arch.generator';
|
||||||
import { generateReport } from './src/generators/report.generator';
|
import { generateReport } from './src/generators/report.generator';
|
||||||
|
import { lintGeneratedFiles } from './src/generators/lint.generator';
|
||||||
import { findEnvironmentFile, parseApiKeys } from './src/utils/environment-finder';
|
import { findEnvironmentFile, parseApiKeys } from './src/utils/environment-finder';
|
||||||
import { askApiKeysForTags, askSelectionFilter } from './src/utils/prompt';
|
import { askApiKeysForTags, askSelectionFilter } from './src/utils/prompt';
|
||||||
import type { SelectionFilter } from './src/types';
|
import type { SelectionFilter } from './src/types';
|
||||||
@@ -37,6 +38,7 @@ program
|
|||||||
.option('-t, --templates <dir>', 'Custom templates directory', path.join(__dirname, 'templates'))
|
.option('-t, --templates <dir>', 'Custom templates directory', path.join(__dirname, 'templates'))
|
||||||
.option('--skip-install', 'Skip dependency installation')
|
.option('--skip-install', 'Skip dependency installation')
|
||||||
.option('--dry-run', 'Simulate without generating files')
|
.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')
|
.option('-s, --select-endpoints', 'Interactively select which tags and endpoints to generate')
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
@@ -132,6 +134,10 @@ async function main(): Promise<void> {
|
|||||||
);
|
);
|
||||||
cleanup(tempDir);
|
cleanup(tempDir);
|
||||||
|
|
||||||
|
if (!options.skipLint) {
|
||||||
|
lintGeneratedFiles(options.output);
|
||||||
|
}
|
||||||
|
|
||||||
const report = generateReport(options.output, analysis);
|
const report = generateReport(options.output, analysis);
|
||||||
|
|
||||||
console.log('\n' + '='.repeat(60));
|
console.log('\n' + '='.repeat(60));
|
||||||
|
|||||||
106
src/generators/lint.generator.ts
Normal file
106
src/generators/lint.generator.ts
Normal file
@@ -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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,4 +9,5 @@ export interface CliOptions {
|
|||||||
skipInstall?: boolean;
|
skipInstall?: boolean;
|
||||||
dryRun?: boolean;
|
dryRun?: boolean;
|
||||||
selectEndpoints?: boolean;
|
selectEndpoints?: boolean;
|
||||||
|
skipLint?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user