feat: automatic workspace config

Added feature to detect automatically monorepos and it's
tsconfig.json and jsconfig.json files
This commit is contained in:
Guz013
2023-07-17 18:16:35 -03:00
parent 03f9758884
commit f540e1e7e3
8 changed files with 118 additions and 10 deletions

View File

@@ -1,7 +1,6 @@
import { defineConfig } from 'readable';
export default defineConfig({
tsconfig: ['./tsconfig.json', './packages/*/tsconfig.json', './packages/*/jsconfig.json'],
environment: {
node: true,
},

View File

@@ -1,3 +1,3 @@
import type { Config, ESConfig } from './src/types';
export function defineConfig(config: Config): ESConfig[];
export async function defineConfig(config: Config): Promise<ESConfig[]>;

View File

@@ -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"
}
}

10
packages/core/src/@types/globals.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
declare module 'globals' {
const globals: {
builtin: Record<string, boolean>
browser: Record<string, boolean>
node: Record<string, boolean>
nodeBuiltin: Record<string, boolean>
commonjs: Record<string, boolean>
};
export default globals;
}

View File

@@ -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: {

View File

@@ -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<string[]>}
*/
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<string[]>}
*/
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;
}

14
pnpm-lock.yaml generated
View File

@@ -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'}

View File

@@ -13,6 +13,6 @@
"alwaysStrict": true,
"outDir": "./dir"
},
"include": ["**/*.ts", "**/*.js"],
"include": ["eslint.config.js", "commitlint.config.cjs"],
"exclude": ["./node_modules/**", "./dist/**"]
}