UNPKG

@backstage/cli

Version:

CLI for developing Backstage plugins and apps

277 lines (261 loc) • 8.14 kB
/* * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const { join: joinPath } = require('path'); /** * Creates a ESLint configuration that extends the base Backstage configuration. * In addition to the standard ESLint configuration options, the `extraConfig` * parameter also accepts the following keys: * * - `tsRules`: Additional ESLint rules to apply to TypeScript * - `testRules`: Additional ESLint rules to apply to tests * - `restrictedImports`: Additional paths to add to no-restricted-imports * - `restrictedSrcImports`: Additional paths to add to no-restricted-imports in src files * - `restrictedTestImports`: Additional paths to add to no-restricted-imports in test files * - `restrictedSyntax`: Additional patterns to add to no-restricted-syntax * - `restrictedSrcSyntax`: Additional patterns to add to no-restricted-syntax in src files * - `restrictedTestSyntax`: Additional patterns to add to no-restricted-syntax in test files */ function createConfig(dir, extraConfig = {}) { const { extends: extraExtends, plugins, env, parserOptions, ignorePatterns, overrides, rules, tsRules, testRules, restrictedImports, restrictedSrcImports, restrictedTestImports, restrictedSyntax, restrictedSrcSyntax, restrictedTestSyntax, ...otherConfig } = extraConfig; return { ...otherConfig, extends: [ '@spotify/eslint-config-base', '@spotify/eslint-config-typescript', 'prettier', 'plugin:jest/recommended', 'plugin:@backstage/recommended', ...(extraExtends ?? []), ], parser: '@typescript-eslint/parser', plugins: ['import', ...(plugins ?? [])], env: { jest: true, ...env, }, parserOptions: { ecmaVersion: 2018, sourceType: 'module', lib: require('./tsconfig.json').compilerOptions.lib, ...parserOptions, }, ignorePatterns: [ '.eslintrc.*', '**/dist/**', '**/dist-types/**', ...(ignorePatterns ?? []), ], rules: { 'no-shadow': 'off', 'no-redeclare': 'off', '@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-redeclare': 'error', 'no-undef': 'off', 'import/newline-after-import': 'error', 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'error', '@typescript-eslint/consistent-type-assertions': 'error', '@typescript-eslint/no-unused-vars': [ 'warn', { vars: 'all', args: 'after-used', ignoreRestSiblings: true, argsIgnorePattern: '^_', }, ], 'no-restricted-imports': [ 2, { paths: [ ...(restrictedImports ?? []), ...(restrictedSrcImports ?? []), ], patterns: [ // Prevent imports of stories or tests '*.stories*', '*.test*', '**/__testUtils__/**', '**/__mocks__/**', ], }, ], 'no-restricted-syntax': [ 'error', ...(restrictedSyntax ?? []), ...(restrictedSrcSyntax ?? []), ], ...rules, }, overrides: [ { files: ['**/*.ts?(x)'], rules: { '@typescript-eslint/no-unused-vars': 'off', 'no-undef': 'off', ...tsRules, }, }, { files: [ '**/*.test.*', '**/*.stories.*', '**/__testUtils__/**', '**/__mocks__/**', 'src/setupTests.*', '!src/**', ], rules: { ...testRules, 'no-restricted-syntax': [ 'error', ...(restrictedSyntax ?? []), ...(restrictedTestSyntax ?? []), ], 'no-restricted-imports': [ 2, { paths: [ ...(restrictedImports ?? []), ...(restrictedTestImports ?? []), ], }, ], }, }, ...(overrides ?? []), ], }; } /** * Creates a ESLint configuration for the given package role. * The `extraConfig` parameter accepts the same keys as for the `createConfig` function. */ function createConfigForRole(dir, role, extraConfig = {}) { switch (role) { case 'common-library': return createConfig(dir, extraConfig); case 'web-library': case 'frontend': case 'frontend-plugin': case 'frontend-plugin-module': return createConfig(dir, { ...extraConfig, extends: [ '@spotify/eslint-config-react', ...(extraConfig.extends ?? []), ], parserOptions: { ecmaFeatures: { jsx: true, }, ...extraConfig.parserOptions, }, settings: { react: { version: 'detect', }, ...extraConfig.settings, }, restrictedImports: [ { // Importing the entire MUI icons packages impedes build performance as the list of icons is huge. name: '@material-ui/icons', message: "Please import '@material-ui/icons/<Icon>' instead.", }, { name: '@material-ui/icons/', // because this is possible too ._. message: "Please import '@material-ui/icons/<Icon>' instead.", }, ...require('module').builtinModules, ...(extraConfig.restrictedImports ?? []), ], tsRules: { 'react/prop-types': 0, ...extraConfig.tsRules, }, }); case 'cli': case 'node-library': case 'backend': case 'backend-plugin': case 'backend-plugin-module': return createConfig(dir, { ...extraConfig, globals: { __non_webpack_require__: 'readonly', ...extraConfig.globals, }, rules: { 'no-console': 0, // Permitted in console programs 'new-cap': ['error', { capIsNew: false }], // Because Express constructs things e.g. like 'const r = express.Router()' ...extraConfig.rules, }, restrictedSyntax: [ { message: 'Default import from winston is not allowed, import `* as winston` instead.', selector: 'ImportDeclaration[source.value="winston"] ImportDefaultSpecifier', }, ...(extraConfig.restrictedSyntax ?? []), ], restrictedSrcSyntax: [ { message: "`__dirname` doesn't refer to the same dir in production builds, try `resolvePackagePath()` from `@backstage/backend-common` instead.", selector: 'Identifier[name="__dirname"]', }, ...(extraConfig.restrictedSrcSyntax ?? []), ], }); default: throw new Error(`Unknown package role ${role}`); } } /** * Creates a ESLint configuration for the package in the given directory. * The `extraConfig` parameter accepts the same keys as for the `createConfig` function. */ function createPackageConfig(dir, extraConfig) { const pkg = require(joinPath(dir, 'package.json')); const role = pkg.backstage?.role; if (!role) { throw new Error(`Package ${pkg.name} does not have a backstage.role`); } return createConfigForRole(dir, role, extraConfig); } module.exports = createPackageConfig; // Alias module.exports.createConfig = createConfig; module.exports.createConfigForRole = createConfigForRole; module.exports.createPackageConfig = createPackageConfig;