Skip to content

Implement setup wizard for ESLint config #47

@matejchalk

Description

@matejchalk

User story

See parent issue:

Usage examples

Default config only

Input

npm init @code-pushup/cli

Which configurations would you like to set up?

  • JavaScript (default)
  • TypeScript (strict)
  • Node.js
  • Angular
  • Angular & NgRx
  • React
  • GraphQL (server)
  • Jest
  • Vitest
  • Cypress
  • Playwright
  • Storybook
  • React Testing Library

Output

Installs required peer dependencies:

npm install --save-dev \
  eslint@^9.0.0 \
  @eslint/js@^9.0.0 \
  "eslint-plugin-functional@^7.0.0 || ^8.0.0 || ^9.0.0" \
  eslint-plugin-import@^2.31.0 \
  "eslint-plugin-promise@>=6.4.0" \
  eslint-plugin-sonarjs@^1.0.4 \
  "eslint-plugin-unicorn@>=50.0.0" \
  "globals@>=14.0.0" \
  typescript-eslint@^8.0.0

Installs our config package:

npm install --save-dev @code-pushup/eslint-config

Creates eslint.config.js:

import javascript from '@code-pushup/eslint-config/javascript.js';
import { defineConfig } from 'eslint/config';

export default defineConfig(...javascript);
Multiple configs, with additional configuration steps

Input

npm init @code-pushup/cli

Which configurations would you like to set up?

  • JavaScript (default)
  • TypeScript (strict)
  • Node.js
  • Angular
  • Angular & NgRx
  • React
  • GraphQL (server)
  • Jest
  • Vitest
  • Cypress
  • Playwright
  • Storybook
  • React Testing Library

The selected "TypeScript (strict)" configuration requires additional setup. Which tsconfig should be used?

  • Enter file path: tsconfig.json

Output

Installs required peer dependencies:

npm install --save-dev \
  eslint@^9.0.0 \
  @eslint/js@^9.0.0 \
  "eslint-plugin-functional@^7.0.0 || ^8.0.0 || ^9.0.0" \
  eslint-plugin-import@^2.31.0 \
  "eslint-plugin-promise@>=6.4.0" \
  eslint-plugin-sonarjs@^1.0.4 \
  "eslint-plugin-unicorn@>=50.0.0" \
  "globals@>=14.0.0" \
  typescript-eslint@^8.0.0

Installs optional peer dependencies that are required by selected configs:

npm install --save-dev \
  "eslint-import-resolver-typescript@^3.0.0 || ^4.0.0" \
  @vitest/eslint-plugin@^1.1.9

Installs our config package:

npm install --save-dev @code-pushup/eslint-config

Creates eslint.config.js, including all configs and their additional setup steps:

import typescript from '@code-pushup/eslint-config/typescript.js';
import vitest from '@code-pushup/eslint-config/vitest.js';
import { defineConfig } from 'eslint/config';

export default defineConfig(
  ...typescript,
  ...vitest,
  {
    files: ['**/*.ts'],
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
 {
    settings: {
      'import/resolver': {
        typescript: {
          alwaysTryTypes: true,
          project: 'tsconfig.json',
        },
      },
    },
  },
);
Extending existing config

Input

The user has the following eslint.config.js:

import jsxA11y from 'eslint-plugin-jsx-a11y';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';

export default [
  react.configs.flat.recommended,
  react.configs.flat['jsx-runtime'],
  jsxA11y.flatConfigs.recommended,
  {
    plugins: { 'react-hooks': reactHooks },
    rules: {
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': 'warn',
    }
  },
];

The user runs npm init @code-pushup/eslint-config and selects React and React Testing Library configs.

Output

The selected configs are appended to the flat config array.

import jsxA11y from 'eslint-plugin-jsx-a11y';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import cpReact from '@code-pushup/eslint-config/react.js';
import cpReactTestingLibrary from '@code-pushup/eslint-config/react-testing-library.js';

export default [
  react.configs.flat.recommended,
  react.configs.flat['jsx-runtime'],
  jsxA11y.flatConfigs.recommended,
  {
    plugins: { 'react-hooks': reactHooks },
    rules: {
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': 'warn',
    }
  },
  ...cpReact,
  ...cpReactTestingLibrary,
];

Implementation

In general, use the same approach as for @code-pushup/create-cli.

Acceptance criteria

  • The setup wizard can be triggered by npm init command.
  • Alternatively, the setup wizard can be imported and invoked programmatically.
  • Inputs are handled as interactive user prompts by default, but may be skipped if an equivalent CLI argument is supplied.
  • The setup wizard prompts the user to select a subset of the available configs.
  • Configs are pre-selected in the prompt based on information from the user's current working directory.
    • The javascript config is always pre-selected.
    • The typescript config is pre-selected if there's a tsconfig.json file or typescript is installed.
    • The node config is pre-selected if any well-known backend frameworks (express, @nestjs/core, @apollo/server, fastify) or full-stack frameworks (next, @angular/ssr, nuxt, @sveltejs/kit) are installed.
    • The angular config is pre-selected if @angular/core is installed.
    • The ngrx config is pre-selected if @ngrx/core is installed.
    • The react config is pre-selected if react is installed.
    • The graphql config is pre-selected if one of @apollo/server, graphql-yoga, or type-graphql is installed.
    • The jest config is pre-selected if jest is installed or there's a /^jest\.config\.[cm]?[jt]s$/ file.
    • The vitest config is pre-selected if vitest is installed or there's a /^(vite|vitest)\.config\.m?[jt]s$/ file.
    • The cypress config is pre-selected if cypress is installed or there's a /^cypress\.config\.[cm]?[jt]s$/ file.
    • The playwright config is pre-selected if @playwright/test is installed or there's a playwright.config.ts file.
    • The storybook config is pre-selected if storybook is installed or there's a .storybook folder.
    • The react-testing-library config is pre-selected if @testing-library/react is installed.
  • If a config with additional setup steps is selected, then there are additional user prompts.
    • If typescript is selected, then the user should provide their tsconfig path. The default value should be tsconfig.json or any tsconfig.*.json if such a file is found.
    • If node is selected, then the user should provide their Node version source.
      • Their options are (in descending order of priority):
        • Use .node-version file - Pre-selected if such a file exists. If selected, then settings.node.version is set to fs.readFileSync('.node-version', 'utf8') in eslint.config.js.
        • Use engines.node from package.json - Pre-selected if field exists in package.json.
        • Enter Node version range - If selected, then settings.node.version is set to a user-submitted value.
      • If the user chose to enter a version manually - or they picked another option and it doesn't exist yet at that location - they are prompted to enter a Node version range (e.g., >=24.8.0). This value is validated and written to the selected location.
  • All required peer dependencies are installed.
  • If configs with additional peer dependencies are selected, those peer dependencies are also installed.
  • The @code-pushup/eslint-config package is installed.
  • If there is no eslint.config.js, then it is created. The selected configs are included in it.
  • If an eslint.config.js already exists, then it is modified. The selected configs are appended to the flat config array.
    • If the eslint.config.js file is in CommonJS format, then the user is informed that Only ESLint configs in ESM format are supported and the setup wizard terminates without making any changes.
    • The file modifications handle exports with or without config functions (defineConfig or tseslint.config).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions