From f540e1e7e37814002879153cf77463a0c7dc4bc3 Mon Sep 17 00:00:00 2001 From: Guz013 <43732358+Guz013@users.noreply.github.com> Date: Mon, 17 Jul 2023 18:16:35 -0300 Subject: [PATCH] feat: automatic workspace config Added feature to detect automatically monorepos and it's tsconfig.json and jsconfig.json files --- eslint.config.js | 1 - packages/core/index.d.ts | 2 +- packages/core/package.json | 5 +- packages/core/src/@types/globals.d.ts | 10 +++ packages/core/src/index.js | 7 ++- packages/core/src/tsconfigs.js | 87 +++++++++++++++++++++++++++ pnpm-lock.yaml | 14 ++++- tsconfig.json | 2 +- 8 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 packages/core/src/@types/globals.d.ts create mode 100644 packages/core/src/tsconfigs.js diff --git a/eslint.config.js b/eslint.config.js index 542b62e..05e2725 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,7 +1,6 @@ import { defineConfig } from 'readable'; export default defineConfig({ - tsconfig: ['./tsconfig.json', './packages/*/tsconfig.json', './packages/*/jsconfig.json'], environment: { node: true, }, diff --git a/packages/core/index.d.ts b/packages/core/index.d.ts index 89e3dfa..3f8c3ac 100644 --- a/packages/core/index.d.ts +++ b/packages/core/index.d.ts @@ -1,3 +1,3 @@ import type { Config, ESConfig } from './src/types'; -export function defineConfig(config: Config): ESConfig[]; +export async function defineConfig(config: Config): Promise; diff --git a/packages/core/package.json b/packages/core/package.json index 74db16a..3acafe6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -23,13 +23,14 @@ "@eslint/js": "^8.44.0", "@types/eslint__js": "^8.42.0", "@types/node": "^20.4.1", - "eslint": "^8.44.0" + "eslint": "^8.44.0", + "typescript": "^5.1.6" }, "dependencies": { "@eslint/eslintrc": "^2.1.0", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "globals": "^13.20.0", - "typescript": "^5.0.0" + "yaml": "^2.3.1" } } diff --git a/packages/core/src/@types/globals.d.ts b/packages/core/src/@types/globals.d.ts new file mode 100644 index 0000000..30c7b93 --- /dev/null +++ b/packages/core/src/@types/globals.d.ts @@ -0,0 +1,10 @@ +declare module 'globals' { + const globals: { + builtin: Record + browser: Record + node: Record + nodeBuiltin: Record + commonjs: Record + }; + export default globals; +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index a8df4a0..00ad2c7 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -3,6 +3,7 @@ import tsEslint from '@typescript-eslint/eslint-plugin'; import tsParser from '@typescript-eslint/parser'; import js from '@eslint/js'; import * as configs from './configs/index.js'; +import { getTsConfigs } from './tsconfigs.js'; /** * @param {import('./types').Config} userConfig @@ -12,6 +13,8 @@ import * as configs from './configs/index.js'; export async function defineConfig(userConfig) { userConfig.strict ??= true; + userConfig.rootDir ??= process.cwd(); + userConfig.tsconfig ??= await getTsConfigs(userConfig.rootDir); process.env.READABLE_ESLINT_STRICT = userConfig.strict; process.env.READABLE_ESLINT_OPTIONS = { @@ -33,7 +36,7 @@ export async function defineConfig(userConfig) { }, js.configs.recommended, { - files: ['**/*.js', '**/*.ts'], + files: ['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'], plugins: { '@typescript-eslint': tsEslint, }, @@ -42,7 +45,7 @@ export async function defineConfig(userConfig) { parser: tsParser, parserOptions: { project: userConfig.tsconfig, - tsconfigRootDir: userConfig.rootDir ?? process.cwd(), + tsconfigRootDir: userConfig.rootDir, }, }, rules: { diff --git a/packages/core/src/tsconfigs.js b/packages/core/src/tsconfigs.js new file mode 100644 index 0000000..4df55bf --- /dev/null +++ b/packages/core/src/tsconfigs.js @@ -0,0 +1,87 @@ +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { join, normalize } from 'node:path'; + +/** @type {(...path: string[]) => string} */ +function toPath(...path) { + return normalize(join(...path)); +} + +/** @type {(...path: string[]) => boolean} */ +function exists(...path) { + return existsSync(toPath(...path)); +} + +/** + * @param {string} directory + * @returns {Promise} + */ +async function getMonorepoConfigs(directory) { + + /** @type {string[]} */ + const paths = []; + + if (exists(directory, 'pnpm-workspace.yaml') || exists(directory, 'pnpm-workspace.yml')) { + + const YAML = await import('yaml'); + + const yamlFilePath = exists(directory, 'pnpm-workspace.yaml') + ? join(directory, 'pnpm-workspace.yaml') + : join(directory, 'pnpm-workspace.yml'); + + /** @type {{packages?: string[], [properties: string]: unknown}} */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const pnpmWorkspaces = YAML.parse(await readFile(yamlFilePath, 'utf-8')); + + const files = pnpmWorkspaces.packages?.map(w => [ + toPath(directory, w, 'tsconfig.json'), + toPath(directory, w, 'jsconfig.json'), + ]).flat() ?? []; + + paths.push(...files); + + } + else if (exists(directory, 'package.json')) { + /** @type {{workspaces?: string[], [properties: string]: unknown}} */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const packageJson = JSON.parse(await readFile(join(directory, 'package.json'), 'utf-8')); + + const files = packageJson.workspaces?.map(w => [ + toPath(directory, w, 'tsconfig.json'), + toPath(directory, w, 'jsconfig.json'), + ]).flat() ?? []; + + paths.push(...files); + + } + + return paths; + +} + +/** + * @param {string} directory + * @returns {Promise} + */ +export async function getTsConfigs(directory) { + + const rootTSConfig = exists(directory, 'tsconfig.eslint.json') + ? toPath(directory, 'tsconfig.eslint.json') + : exists(directory, 'tsconfig.json') + ? toPath(directory, 'tsconfig.json') + : undefined; + + const rootJSConfig = exists(directory, 'jsconfig.eslint.json') + ? toPath(directory, 'jsconfig.eslint.json') + : exists(directory, 'jsconfig.json') + ? toPath(directory, 'jsconfig.json') + : undefined; + + const monorepoConfigs = await getMonorepoConfigs(directory); + + const paths = /** @type {string[]} */ + ([rootTSConfig, rootJSConfig, ...monorepoConfigs]).filter(p => p); + + return paths; + +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2937b6b..f97bc9a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,9 +87,9 @@ importers: globals: specifier: ^13.20.0 version: 13.20.0 - typescript: - specifier: ^5.0.0 - version: 5.1.6 + yaml: + specifier: ^2.3.1 + version: 2.3.1 devDependencies: '@eslint/js': specifier: ^8.44.0 @@ -103,6 +103,9 @@ importers: eslint: specifier: ^8.44.0 version: 8.44.0 + typescript: + specifier: ^5.1.6 + version: 5.1.6 packages: @@ -3476,6 +3479,11 @@ packages: engines: {node: '>= 6'} dev: true + /yaml@2.3.1: + resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} + engines: {node: '>= 14'} + dev: false + /yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} diff --git a/tsconfig.json b/tsconfig.json index 79989dc..e8b28d3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,6 @@ "alwaysStrict": true, "outDir": "./dir" }, - "include": ["**/*.ts", "**/*.js"], + "include": ["eslint.config.js", "commitlint.config.cjs"], "exclude": ["./node_modules/**", "./dist/**"] }