@baseplate-dev/tools 0.5.1 → 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,85 +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
+ * 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 },
36
+ },
37
+ extends: [
38
+ reactPlugin.configs.flat?.recommended,
39
+ reactPlugin.configs.flat?.['jsx-runtime'],
40
+ reactJsxA11yPlugin.flatConfigs.recommended,
41
+ ],
42
+ settings: {
43
+ react: {
44
+ version: 'detect',
45
+ },
46
+ },
25
47
  },
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',
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
+ ],
34
67
  },
35
68
  },
36
- },
37
69
 
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 } },
48
- ],
49
- // Allow floating navigate from useNavigate to be handled by the router
50
- '@typescript-eslint/no-floating-promises': [
51
- 'error',
52
- {
53
- allowForKnownSafeCalls: ['UseNavigateResult'],
54
- },
55
- ],
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
+ },
56
80
  },
57
- },
58
81
 
59
- // React Hooks
60
- reactHooksPlugin.configs['recommended-latest'],
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,
61
85
 
62
- // Import-X
63
- eslintPluginImportX.flatConfigs.react,
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
+ },
98
+ },
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
+ : []),
64
116
 
65
- // Unicorn
66
- {
67
- rules: {
68
- // Support kebab case with - prefix to support ignored files in routes and $ prefix for Tanstack camelCase files
69
- 'unicorn/filename-case': [
70
- 'error',
71
- {
72
- cases: {
73
- kebabCase: true,
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
+ ],
74
131
  },
75
- ignore: [
76
- String.raw`^-[a-z0-9\-\.]+$`,
77
- String.raw`^\$[a-zA-Z0-9\.]+$`,
78
- ],
79
- },
80
- ],
132
+ ],
133
+ },
81
134
  },
82
- },
83
135
 
84
- // Global ignores
85
- {
86
- ignores: ['**/route-tree.gen.ts'],
87
- },
88
- );
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,
@@ -132,6 +132,9 @@ export function generateTypescriptEslintConfig(options = []) {
132
132
  allow: ['NotFoundError', 'Redirect'],
133
133
  },
134
134
  ],
135
+ '@typescript-eslint/no-unused-vars': KEEP_UNUSED_IMPORTS
136
+ ? 'error'
137
+ : 'off',
135
138
  },
136
139
  },
137
140
 
@@ -143,9 +146,6 @@ export function generateTypescriptEslintConfig(options = []) {
143
146
  rules: {
144
147
  // Prevent unused imports from being auto-removed if the environment variable is set to true
145
148
  // This is useful when AI agents are editing code part by part
146
- '@typescript-eslint/no-unused-vars': KEEP_UNUSED_IMPORTS
147
- ? 'error'
148
- : 'off',
149
149
  'unused-imports/no-unused-imports': KEEP_UNUSED_IMPORTS
150
150
  ? 'off'
151
151
  : 'error',
@@ -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.1",
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",
@@ -49,36 +49,37 @@
49
49
  "src-subpath-import-plugin.js"
50
50
  ],
51
51
  "dependencies": {
52
- "@eslint/js": "9.32.0",
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.3.4",
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
- "eslint-plugin-perfectionist": "4.15.0",
61
+ "eslint-plugin-perfectionist": "5.4.0",
61
62
  "eslint-plugin-react": "7.37.5",
62
- "eslint-plugin-react-hooks": "5.2.0",
63
- "eslint-plugin-storybook": "10.1.10",
64
- "eslint-plugin-unicorn": "60.0.0",
63
+ "eslint-plugin-react-hooks": "7.0.1",
64
+ "eslint-plugin-storybook": "10.2.3",
65
+ "eslint-plugin-unicorn": "62.0.0",
65
66
  "eslint-plugin-unused-imports": "4.1.4",
66
- "globals": "16.4.0",
67
- "prettier-plugin-packagejson": "2.5.19",
68
- "prettier-plugin-tailwindcss": "0.6.14",
69
- "typescript-eslint": "8.38.0"
67
+ "globals": "17.3.0",
68
+ "prettier-plugin-packagejson": "3.0.0",
69
+ "prettier-plugin-tailwindcss": "0.7.2",
70
+ "typescript-eslint": "8.54.0"
70
71
  },
71
72
  "devDependencies": {
72
- "@types/eslint-plugin-jsx-a11y": "6.10.0",
73
- "eslint": "9.32.0",
74
- "prettier": "3.6.2",
75
- "typescript": "5.8.3",
73
+ "@types/eslint-plugin-jsx-a11y": "6.10.1",
74
+ "eslint": "9.39.2",
75
+ "prettier": "3.8.1",
76
+ "typescript": "5.9.3",
76
77
  "vite": "7.1.12"
77
78
  },
78
79
  "peerDependencies": {
79
- "eslint": "9.32.0",
80
+ "eslint": "9.39.2",
80
81
  "react": "19.1.0",
81
- "typescript": "5.8.3",
82
+ "typescript": "5.9.3",
82
83
  "vitest": "4.0.16"
83
84
  },
84
85
  "engines": {
@@ -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
  };