@neurolint/cli
Version:
NeuroLint CLI for React/Next.js modernization with advanced 6-layer orchestration and intelligent AST transformations
254 lines (229 loc) • 6.89 kB
text/typescript
import chalk from "chalk";
import ora from "ora";
import inquirer from "inquirer";
import fs from "fs-extra";
import path from "path";
interface InitOptions {
force?: boolean;
}
const defaultConfig = {
version: "1.0.0",
layers: {
enabled: [1, 2, 3, 4],
config: {
1: { name: "TypeScript & Next.js Config Modernization", timeout: 30000 },
2: { name: "Legacy Pattern Cleanup", timeout: 45000 },
3: { name: "React 18 & Component Modernization", timeout: 60000 },
4: { name: "SSR/Hydration Migration Safety", timeout: 45000 },
5: { name: "Next.js App Router Migration", timeout: 30000, enabled: false },
6: { name: "Enterprise Quality & Performance", timeout: 30000, enabled: false },
},
},
files: {
include: ["**/*.{ts,tsx,js,jsx}"],
exclude: [
"node_modules/**",
"dist/**",
"build/**",
".next/**",
"coverage/**",
],
},
output: {
format: "table",
verbose: false,
},
api: {
url: "https://app.neurolint.dev/api",
timeout: 60000,
},
};
export async function initCommand(options: InitOptions) {
const configPath = path.join(process.cwd(), ".neurolint.json");
const spinner = ora("Initializing modernization configuration...").start();
try {
// Check if config already exists
if ((await fs.pathExists(configPath)) && !options.force) {
spinner.stop();
const { overwrite } = await inquirer.prompt([
{
type: "confirm",
name: "overwrite",
message: "NeuroLint configuration already exists. Overwrite?",
default: false,
},
]);
if (!overwrite) {
console.log(chalk.yellow("Configuration initialization cancelled."));
return;
}
}
spinner.text = "Detecting project structure...";
// Detect project type
const packageJsonPath = path.join(process.cwd(), "package.json");
let projectType = "javascript";
let framework = "none";
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);
const deps = {
...packageJson.dependencies,
...packageJson.devDependencies,
};
if (deps.typescript || deps["@types/node"]) {
projectType = "typescript";
}
if (deps.next) {
framework = "nextjs";
} else if (deps.react) {
framework = "react";
} else if (deps.vue) {
framework = "vue";
}
}
spinner.succeed(
`Detected ${projectType} project with ${framework} framework`,
);
// Interactive configuration
console.log(chalk.blue("\nLet's configure NeuroLint for your project:\n"));
const answers = await inquirer.prompt([
{
type: "checkbox",
name: "layers",
message: "Which NeuroLint layers would you like to enable?",
choices: [
{
name: "Layer 1: Configuration Validation (tsconfig, package.json)",
value: 1,
checked: true,
},
{
name: "Layer 2: Pattern & Entity Fixes (HTML entities, old patterns)",
value: 2,
checked: true,
},
{
name: "Layer 3: Component Best Practices (keys, accessibility)",
value: 3,
checked: true,
},
{
name: "Layer 4: Hydration & SSR Guard (SSR protection)",
value: 4,
checked: true,
},
{
name: "Layer 5: Next.js Optimization (App Router patterns)",
value: 5,
checked: framework === "nextjs",
},
{
name: "Layer 6: Quality & Performance (error handling)",
value: 6,
checked: false,
},
],
},
{
type: "input",
name: "apiUrl",
message: "NeuroLint API URL:",
default: process.env.NEUROLINT_API_URL || "https://app.neurolint.dev/api",
validate: (input) => {
try {
new URL(input);
return true;
} catch {
return "Please enter a valid URL";
}
},
},
{
type: "list",
name: "outputFormat",
message: "Default output format:",
choices: ["table", "json", "summary"],
default: "table",
},
{
type: "confirm",
name: "verbose",
message: "Enable verbose output by default?",
default: false,
},
]);
// Create configuration
const config = {
...defaultConfig,
layers: {
...defaultConfig.layers,
enabled: answers.layers,
},
api: {
...defaultConfig.api,
url: answers.apiUrl,
},
output: {
format: answers.outputFormat,
verbose: answers.verbose,
},
};
// Customize file patterns based on project type
if (projectType === "typescript") {
config.files.include = ["**/*.{ts,tsx}"];
}
if (framework === "nextjs") {
config.files.exclude.push("pages/**", "app/**/_*");
}
// Write configuration file
await fs.writeJson(configPath, config, { spaces: 2 });
console.log(chalk.green("\nNeuroLint configuration created successfully!"));
console.log(
chalk.blue(`Configuration saved to: ${chalk.white(".neurolint.json")}`),
);
// Create .neurolintignore file
const ignorePath = path.join(process.cwd(), ".neurolintignore");
const ignoreContent = `# NeuroLint ignore patterns
node_modules/
dist/
build/
.next/
coverage/
*.min.js
*.bundle.js
*.d.ts
`;
await fs.writeFile(ignorePath, ignoreContent);
console.log(
chalk.blue(`Ignore file created: ${chalk.white(".neurolintignore")}`),
);
// Show next steps
console.log(chalk.blue("\nNext steps:"));
console.log(
chalk.gray("1. Run analysis: ") + chalk.white("neurolint analyze"),
);
console.log(chalk.gray("2. Fix issues: ") + chalk.white("neurolint fix"));
console.log(
chalk.gray("3. Interactive mode: ") +
chalk.white("neurolint interactive"),
);
console.log(chalk.gray("4. View help: ") + chalk.white("neurolint help"));
// Offer to run initial analysis
const { runAnalysis } = await inquirer.prompt([
{
type: "confirm",
name: "runAnalysis",
message: "Would you like to run an initial analysis now?",
default: true,
},
]);
if (runAnalysis) {
console.log(chalk.blue("\nRunning initial analysis...\n"));
const { analyzeCommand } = await import("./analyze-fixed");
await analyzeCommand([], { layers: answers.layers.join(",") });
}
} catch (error) {
spinner.fail(
`Initialization failed: ${error instanceof Error ? error.message : "Unknown error"}`,
);
}
}