@baseplate-dev/tools 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,94 +4,138 @@
4
4
  * @typedef {import('./typescript.js').GenerateTypescriptEslintConfigOptions} GenerateTypescriptEslintConfigOptions
5
5
  */
6
6
 
7
+ /**
8
+ * @typedef {Object} GenerateReactEslintConfigOptions
9
+ * @property {string | null} [tailwindEntryPoint] - Absolute path to Tailwind CSS entry file (e.g., path.join(import.meta.dirname, 'src/styles.css')). Pass null to disable Tailwind linting.
10
+ */
11
+
12
+ import eslintPluginBetterTailwindcss from 'eslint-plugin-better-tailwindcss';
7
13
  import eslintPluginImportX from 'eslint-plugin-import-x';
8
14
  import reactJsxA11yPlugin from 'eslint-plugin-jsx-a11y';
9
15
  import reactPlugin from 'eslint-plugin-react';
10
16
  import reactHooksPlugin from 'eslint-plugin-react-hooks';
17
+ import { defineConfig } from 'eslint/config';
11
18
  import globals from 'globals';
12
- import tsEslint from 'typescript-eslint';
13
19
 
14
20
  /** @type {GenerateTypescriptEslintConfigOptions} */
15
21
  export const reactTypescriptEslintOptions = {
16
22
  extraDefaultProjectFiles: [],
17
23
  };
18
24
 
19
- export const reactEslintConfig = tsEslint.config(
20
- // React & A11y
21
- {
22
- files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
23
- languageOptions: {
24
- globals: { ...globals.browser },
25
- },
26
- extends: [
27
- reactPlugin.configs.flat?.recommended,
28
- reactPlugin.configs.flat?.['jsx-runtime'],
29
- reactJsxA11yPlugin.flatConfigs.recommended,
30
- ],
31
- settings: {
32
- react: {
33
- version: 'detect',
25
+ /**
26
+ * Generates a React ESLint configuration
27
+ * @param {GenerateReactEslintConfigOptions} options - Configuration options
28
+ */
29
+ export function generateReactEslintConfig(options) {
30
+ return defineConfig(
31
+ // React & A11y
32
+ {
33
+ files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
34
+ languageOptions: {
35
+ globals: { ...globals.browser },
34
36
  },
35
- },
36
- },
37
-
38
- // Typescript
39
- {
40
- files: ['**/*.{tsx,mtsx}'],
41
- rules: {
42
- // Allow promises to be returned from functions for attributes in React
43
- // to allow for React Hook Form handleSubmit to work as expected
44
- // See https://github.com/orgs/react-hook-form/discussions/8020
45
- '@typescript-eslint/no-misused-promises': [
46
- 'error',
47
- { checksVoidReturn: { attributes: false } },
37
+ extends: [
38
+ reactPlugin.configs.flat?.recommended,
39
+ reactPlugin.configs.flat?.['jsx-runtime'],
40
+ reactJsxA11yPlugin.flatConfigs.recommended,
48
41
  ],
49
- // Allow floating navigate from useNavigate to be handled by the router
50
- '@typescript-eslint/no-floating-promises': [
51
- 'error',
52
- {
53
- allowForKnownSafeCalls: ['UseNavigateResult'],
42
+ settings: {
43
+ react: {
44
+ version: 'detect',
54
45
  },
55
- ],
46
+ },
47
+ },
48
+
49
+ // Typescript
50
+ {
51
+ files: ['**/*.{tsx,mtsx}'],
52
+ rules: {
53
+ // Allow promises to be returned from functions for attributes in React
54
+ // to allow for React Hook Form handleSubmit to work as expected
55
+ // See https://github.com/orgs/react-hook-form/discussions/8020
56
+ '@typescript-eslint/no-misused-promises': [
57
+ 'error',
58
+ { checksVoidReturn: { attributes: false } },
59
+ ],
60
+ // Allow floating navigate from useNavigate to be handled by the router
61
+ '@typescript-eslint/no-floating-promises': [
62
+ 'error',
63
+ {
64
+ allowForKnownSafeCalls: ['UseNavigateResult'],
65
+ },
66
+ ],
67
+ },
56
68
  },
57
- },
58
69
 
59
- // React Hooks
60
- reactHooksPlugin.configs.flat['recommended-latest'],
61
- {
62
- rules: {
63
- // Disable new strict rules from react-hooks v7 until we enable React Compiler
64
- 'react-hooks/refs': 'off',
65
- 'react-hooks/set-state-in-effect': 'off',
66
- 'react-hooks/preserve-manual-memoization': 'off',
67
- 'react-hooks/incompatible-library': 'off',
70
+ // React Hooks
71
+ reactHooksPlugin.configs.flat['recommended-latest'],
72
+ {
73
+ rules: {
74
+ // Disable new strict rules from react-hooks v7 until we enable React Compiler
75
+ 'react-hooks/refs': 'off',
76
+ 'react-hooks/set-state-in-effect': 'off',
77
+ 'react-hooks/preserve-manual-memoization': 'off',
78
+ 'react-hooks/incompatible-library': 'off',
79
+ },
68
80
  },
69
- },
70
81
 
71
- // Import-X
72
- eslintPluginImportX.flatConfigs.react,
82
+ // Import-X
83
+ // @ts-ignore - bug with incompatible types between @types/eslint and typescript eslint config - https://github.com/un-ts/eslint-plugin-import-x/issues/421
84
+ eslintPluginImportX.flatConfigs.react,
73
85
 
74
- // Unicorn
75
- {
76
- rules: {
77
- // Support kebab case with - prefix to support ignored files in routes and $ prefix for Tanstack camelCase files
78
- 'unicorn/filename-case': [
79
- 'error',
80
- {
81
- cases: {
82
- kebabCase: true,
86
+ // Better Tailwindcss - correctness rules only (formatting handled by Prettier)
87
+ // Only enable if tailwindEntryPoint is provided (not null/undefined)
88
+ ...(options.tailwindEntryPoint !== null &&
89
+ options.tailwindEntryPoint !== undefined
90
+ ? [
91
+ eslintPluginBetterTailwindcss.configs['correctness'],
92
+ {
93
+ settings: {
94
+ 'better-tailwindcss': {
95
+ entryPoint: options.tailwindEntryPoint,
96
+ },
97
+ },
83
98
  },
84
- ignore: [
85
- String.raw`^-[a-z0-9\-\.]+$`,
86
- String.raw`^\$[a-zA-Z0-9\.]+$`,
87
- ],
88
- },
89
- ],
99
+ {
100
+ rules: {
101
+ // Detect custom component classes defined in @layer components
102
+ 'better-tailwindcss/no-unknown-classes': [
103
+ 'error',
104
+ {
105
+ detectComponentClasses: true,
106
+ // Ignore classes used in custom component classes
107
+ ignore: [
108
+ 'toaster', // Sonner library class
109
+ ],
110
+ },
111
+ ],
112
+ },
113
+ },
114
+ ]
115
+ : []),
116
+
117
+ // Unicorn
118
+ {
119
+ rules: {
120
+ // Support kebab case with - prefix to support ignored files in routes and $ prefix for Tanstack camelCase files
121
+ 'unicorn/filename-case': [
122
+ 'error',
123
+ {
124
+ cases: {
125
+ kebabCase: true,
126
+ },
127
+ ignore: [
128
+ String.raw`^-[a-z0-9\-\.]+$`,
129
+ String.raw`^\$[a-zA-Z0-9\.]+$`,
130
+ ],
131
+ },
132
+ ],
133
+ },
90
134
  },
91
- },
92
135
 
93
- // Global ignores
94
- {
95
- ignores: ['**/route-tree.gen.ts'],
96
- },
97
- );
136
+ // Global ignores
137
+ {
138
+ ignores: ['**/route-tree.gen.ts'],
139
+ },
140
+ );
141
+ }
@@ -1,25 +1,19 @@
1
1
  // @ts-check
2
2
 
3
- /**
4
- * @typedef {import('./typescript.js').GenerateTypescriptEslintConfigOptions} GenerateTypescriptEslintConfigOptions
5
- */
6
-
7
3
  import storybook from 'eslint-plugin-storybook';
8
- import tsEslint from 'typescript-eslint';
4
+ import { defineConfig } from 'eslint/config';
9
5
 
10
- /** @type {GenerateTypescriptEslintConfigOptions} */
11
- export const storybookTypescriptEslintOptions = {
12
- extraDevDependencies: [
13
- // allow dev dependencies for Storybook configuration block
14
- '.storybook/**/*.{js,ts,tsx,jsx}',
15
- // allow dev dependencies for Storybook
16
- '**/*.stories.{js,ts,tsx,jsx}',
17
- // allow dev dependencies for MDX files
18
- '**/*.mdx',
19
- ],
20
- };
6
+ /** @type {string[]} */
7
+ export const storybookTypescriptExtraDevDependencies = [
8
+ // allow dev dependencies for Storybook configuration block
9
+ '.storybook/**/*.{js,ts,tsx,jsx}',
10
+ // allow dev dependencies for Storybook
11
+ '**/*.stories.{js,ts,tsx,jsx}',
12
+ // allow dev dependencies for MDX files
13
+ '**/*.mdx',
14
+ ];
21
15
 
22
- export const storybookEslintConfig = tsEslint.config(
16
+ export const storybookEslintConfig = defineConfig(
23
17
  // Storybook
24
18
  // @ts-ignore -- TypeScript resolution bug where it expects a named export called default
25
19
  storybook.configs['flat/recommended'],
@@ -6,6 +6,7 @@ import { importX } from 'eslint-plugin-import-x';
6
6
  import perfectionist from 'eslint-plugin-perfectionist';
7
7
  import eslintPluginUnicorn from 'eslint-plugin-unicorn';
8
8
  import unusedImports from 'eslint-plugin-unused-imports';
9
+ import { defineConfig } from 'eslint/config';
9
10
  import globals from 'globals';
10
11
  import tsEslint from 'typescript-eslint';
11
12
 
@@ -13,6 +14,7 @@ import noUnusedGeneratorDependencies from './rules/no-unused-generator-dependenc
13
14
 
14
15
  /**
15
16
  * @typedef {Object} GenerateTypescriptEslintConfigOptions
17
+ * @property {string} [rootDir] - Absolute path to tsconfig root directory (for tsconfigRootDir)
16
18
  * @property {string[]} [extraTsFileGlobs] - Additional file globs to lint with Typescript
17
19
  * @property {string[]} [extraDevDependencies] - Additional globs for dev dependencies
18
20
  * @property {string[]} [extraDefaultProjectFiles] - Additional default project files
@@ -23,13 +25,10 @@ const KEEP_UNUSED_IMPORTS =
23
25
 
24
26
  /**
25
27
  * Generates a Typescript ESLint configuration
26
- * @param {GenerateTypescriptEslintConfigOptions[]} [options=[]] - Configuration options
28
+ * @param {GenerateTypescriptEslintConfigOptions} [options={}] - Configuration options
27
29
  */
28
- export function generateTypescriptEslintConfig(options = []) {
29
- const tsFileGlobs = [
30
- '**/*.{ts,tsx}',
31
- ...options.flatMap((option) => option.extraTsFileGlobs ?? []),
32
- ];
30
+ export function generateTypescriptEslintConfig(options = {}) {
31
+ const tsFileGlobs = ['**/*.{ts,tsx}', ...(options.extraTsFileGlobs ?? [])];
33
32
  const devDependencies = [
34
33
  // allow dev dependencies for test files
35
34
  '**/*.test-helper.{js,ts,jsx,tsx}',
@@ -42,13 +41,13 @@ export function generateTypescriptEslintConfig(options = []) {
42
41
  '*.{js,ts}',
43
42
  '.*.{js,ts}',
44
43
  '.workspace-meta/**/*',
45
- ...options.flatMap((option) => option.extraDevDependencies ?? []),
44
+ ...(options.extraDevDependencies ?? []),
46
45
  ];
47
46
  const defaultProjectFiles = [
48
47
  'vitest.config.ts',
49
- ...options.flatMap((option) => option.extraDefaultProjectFiles ?? []),
48
+ ...(options.extraDefaultProjectFiles ?? []),
50
49
  ];
51
- return tsEslint.config(
50
+ return defineConfig(
52
51
  // ESLint Configs for all files
53
52
  eslint.configs.recommended,
54
53
  {
@@ -82,6 +81,7 @@ export function generateTypescriptEslintConfig(options = []) {
82
81
  ],
83
82
  languageOptions: {
84
83
  parserOptions: {
84
+ ...(options.rootDir ? { tsconfigRootDir: options.rootDir } : {}),
85
85
  projectService: {
86
86
  // allow default project for root configs
87
87
  allowDefaultProject: defaultProjectFiles,
@@ -162,6 +162,7 @@ export function generateTypescriptEslintConfig(options = []) {
162
162
  },
163
163
 
164
164
  // Import-X Configs
165
+ // @ts-ignore - bug with incompatible types between @types/eslint and typescript eslint config - https://github.com/un-ts/eslint-plugin-import-x/issues/421
165
166
  importX.flatConfigs.recommended,
166
167
  importX.flatConfigs.typescript,
167
168
  {
@@ -1,25 +1,30 @@
1
1
  // @ts-check
2
2
 
3
- import tsEslint from 'typescript-eslint';
3
+ import { defineConfig } from 'eslint/config';
4
4
 
5
5
  import { prettierEslintConfig } from './eslint-configs/prettier.js';
6
6
  import { generateTypescriptEslintConfig } from './eslint-configs/typescript.js';
7
7
 
8
- /** @typedef {import('./eslint-configs/typescript.js').GenerateTypescriptEslintConfigOptions} GenerateTypescriptEslintConfigOptions */
8
+ /**
9
+ * @typedef {Object} DefineNodeEslintConfigOptions
10
+ * @property {string} dirname - The import.meta.dirname of the calling package
11
+ * @property {string[]} [extraDefaultProjectFiles] - Additional default project files
12
+ * @property {string[]} [ignores] - Additional ignore patterns
13
+ */
9
14
 
10
15
  /**
11
- * Generates a Node.js ESLint configuration with customizable options
12
- * @param {GenerateTypescriptEslintConfigOptions} [options] - Configuration options
16
+ * Defines a Node.js ESLint configuration for a package
17
+ * @param {DefineNodeEslintConfigOptions} options - Configuration options
13
18
  */
14
- export function generateNodeConfig(options = {}) {
15
- return tsEslint.config(
16
- ...generateTypescriptEslintConfig([
17
- {
18
- extraDefaultProjectFiles: options.extraDefaultProjectFiles || [],
19
- },
20
- ]),
19
+ export function defineNodeEslintConfig(options) {
20
+ const { dirname, extraDefaultProjectFiles = [], ignores = [] } = options;
21
+
22
+ return defineConfig(
23
+ ...generateTypescriptEslintConfig({
24
+ rootDir: dirname,
25
+ extraDefaultProjectFiles,
26
+ }),
21
27
  prettierEslintConfig,
28
+ ...(ignores.length > 0 ? [{ ignores }] : []),
22
29
  );
23
30
  }
24
-
25
- export default generateNodeConfig();
@@ -1,24 +1,51 @@
1
1
  // @ts-check
2
2
 
3
- import tsEslint from 'typescript-eslint';
3
+ import { defineConfig } from 'eslint/config';
4
+ import path from 'node:path';
4
5
 
5
6
  import { prettierEslintConfig } from './eslint-configs/prettier.js';
6
- import {
7
- reactEslintConfig,
8
- reactTypescriptEslintOptions,
9
- } from './eslint-configs/react.js';
7
+ import { generateReactEslintConfig } from './eslint-configs/react.js';
10
8
  import {
11
9
  storybookEslintConfig,
12
- storybookTypescriptEslintOptions,
10
+ storybookTypescriptExtraDevDependencies,
13
11
  } from './eslint-configs/storybook.js';
14
12
  import { generateTypescriptEslintConfig } from './eslint-configs/typescript.js';
15
13
 
16
- export default tsEslint.config(
17
- ...generateTypescriptEslintConfig([
18
- reactTypescriptEslintOptions,
19
- storybookTypescriptEslintOptions,
20
- ]),
21
- ...reactEslintConfig,
22
- ...storybookEslintConfig,
23
- prettierEslintConfig,
24
- );
14
+ /**
15
+ * @typedef {Object} DefineReactEslintConfigOptions
16
+ * @property {string} dirname - The import.meta.dirname of the calling package
17
+ * @property {boolean} [includeStorybook=false] - Whether to include Storybook configuration
18
+ * @property {string | null} [tailwindEntryPoint='src/styles.css'] - Path to Tailwind CSS entry file relative to dirname. Pass null to disable Tailwind linting.
19
+ * @property {string[]} [ignores] - Additional ignore patterns
20
+ */
21
+
22
+ /**
23
+ * Defines a React ESLint configuration for a package
24
+ * @param {DefineReactEslintConfigOptions} options - Configuration options
25
+ */
26
+ export function defineReactEslintConfig(options) {
27
+ const {
28
+ dirname,
29
+ includeStorybook = false,
30
+ tailwindEntryPoint = 'src/styles.css',
31
+ ignores = [],
32
+ } = options;
33
+
34
+ const absoluteTailwindPath =
35
+ tailwindEntryPoint === null ? null : path.join(dirname, tailwindEntryPoint);
36
+
37
+ return defineConfig(
38
+ ...generateTypescriptEslintConfig({
39
+ rootDir: dirname,
40
+ extraDevDependencies: includeStorybook
41
+ ? storybookTypescriptExtraDevDependencies
42
+ : [],
43
+ }),
44
+ ...generateReactEslintConfig({
45
+ tailwindEntryPoint: absoluteTailwindPath,
46
+ }),
47
+ ...(includeStorybook ? storybookEslintConfig : []),
48
+ prettierEslintConfig,
49
+ ...(ignores.length > 0 ? [{ ignores }] : []),
50
+ );
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baseplate-dev/tools",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Shared dev configurations for linting, formatting, and testing Baseplate projects",
5
5
  "keywords": [
6
6
  "development-tools",
@@ -52,9 +52,10 @@
52
52
  "@eslint/js": "9.39.2",
53
53
  "@tsconfig/node22": "22.0.5",
54
54
  "@tsconfig/vite-react": "7.0.2",
55
- "@vitest/eslint-plugin": "1.6.6",
55
+ "@vitest/eslint-plugin": "1.6.9",
56
56
  "eslint-config-prettier": "10.1.8",
57
57
  "eslint-import-resolver-typescript": "4.4.4",
58
+ "eslint-plugin-better-tailwindcss": "4.2.0",
58
59
  "eslint-plugin-import-x": "4.16.1",
59
60
  "eslint-plugin-jsx-a11y": "6.10.2",
60
61
  "eslint-plugin-perfectionist": "5.4.0",
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+
2
3
  import { fileURLToPath } from 'node:url';
3
4
 
4
5
  import basePrettierConfig from './prettier.config.node.js';
@@ -11,11 +12,13 @@ import basePrettierConfig from './prettier.config.node.js';
11
12
  /** @type {import("prettier").Config} */
12
13
  export default {
13
14
  ...basePrettierConfig,
15
+ // Tailwind-specific configuration
14
16
  tailwindFunctions: ['clsx', 'cn', 'cva'],
15
17
  tailwindStylesheet: './src/styles.css',
18
+ // Plugins must be specified as absolute paths due to Prettier limitations
19
+ // See: https://github.com/prettier/prettier/issues/8056
16
20
  plugins: [
17
21
  ...(basePrettierConfig.plugins ?? []),
18
- // workaround for this bug: https://github.com/prettier/prettier-vscode/issues/3641
19
22
  fileURLToPath(import.meta.resolve('prettier-plugin-tailwindcss')),
20
23
  ],
21
24
  };