js-style-kit 0.8.1 → 0.8.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-style-kit",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "A zero configuration style guide for ESLint and Prettier",
5
5
  "keywords": [
6
6
  "eslint",
@@ -50,21 +50,21 @@
50
50
  "eslint-import-resolver-typescript": "4.4.4",
51
51
  "eslint-plugin-import-x": "4.16.1",
52
52
  "eslint-plugin-jest": "29.0.1",
53
- "eslint-plugin-jsdoc": "61.1.5",
53
+ "eslint-plugin-jsdoc": "61.1.8",
54
54
  "eslint-plugin-nextjs": "1.1.1",
55
55
  "eslint-plugin-perfectionist": "4.15.1",
56
56
  "eslint-plugin-prefer-arrow-functions": "3.9.1",
57
57
  "eslint-plugin-react": "7.37.5",
58
- "eslint-plugin-react-hooks": "7.0.0",
58
+ "eslint-plugin-react-hooks": "7.0.1",
59
59
  "eslint-plugin-react-refresh": "0.4.24",
60
- "eslint-plugin-storybook": "9.1.13",
60
+ "eslint-plugin-storybook": "9.1.15",
61
61
  "eslint-plugin-turbo": "2.5.8",
62
62
  "eslint-plugin-unicorn": "61.0.2",
63
63
  "eslint-plugin-vitest": "0.5.4",
64
64
  "globals": "16.4.0",
65
65
  "prettier": "3.6.2",
66
66
  "prettier-plugin-css-order": "2.1.2",
67
- "prettier-plugin-curly": "0.3.2",
67
+ "prettier-plugin-curly": "0.4.0",
68
68
  "prettier-plugin-packagejson": "2.5.19",
69
69
  "prettier-plugin-sort-json": "4.1.1",
70
70
  "prettier-plugin-tailwindcss": "0.7.1",
@@ -72,9 +72,9 @@
72
72
  },
73
73
  "devDependencies": {
74
74
  "@repo/typescript-config": "workspace:*",
75
- "@types/bun": "1.3.0",
76
- "@types/node": "22.18.10",
77
- "commander": "14.0.1",
75
+ "@types/bun": "1.3.1",
76
+ "@types/node": "22.18.12",
77
+ "commander": "14.0.2",
78
78
  "glob": "11.0.3",
79
79
  "tsup": "8.5.0",
80
80
  "typescript": "5.9.3"
@@ -24,8 +24,56 @@ export default eslintConfig({
24
24
 
25
25
  Convex rules apply only to files in your `convex` directory: `**/convex/**/*.{ts,js}`
26
26
 
27
+ ## Filename Convention
28
+
29
+ When both Convex and Unicorn configurations are enabled, **Convex files are automatically enforced to use camelCase** naming, regardless of the global Unicorn filename case setting.
30
+
31
+ This is because Convex follows JavaScript/TypeScript conventions where files export functions that become API endpoints, and camelCase is the standard for function names.
32
+
33
+ ### Example
34
+
35
+ ```js
36
+ import { eslintConfig } from "js-style-kit";
37
+
38
+ export default eslintConfig({
39
+ convex: true,
40
+ unicorn: { filenameCase: "kebabCase" }, // Global setting
41
+ });
42
+ ```
43
+
44
+ With this configuration:
45
+
46
+ - **Convex files** (`convex/**/*.{ts,js}`) must use **camelCase**: ✅ `getUserData.ts`, `sendMessage.ts`
47
+ - **All other files** must use **kebabCase**: ✅ `user-service.ts`, `api-client.ts`
48
+
49
+ ### Why camelCase for Convex?
50
+
51
+ Convex files export functions that become your backend API. Using camelCase keeps your filenames consistent with the function names they export:
52
+
53
+ ```typescript
54
+ // convex/getUserData.ts
55
+ export default query(async (ctx) => {
56
+ // Function is called as api.getUserData
57
+ });
58
+ ```
59
+
60
+ ### Disabling Filename Enforcement
61
+
62
+ If you want to disable filename case enforcement for Convex files, you can override it:
63
+
64
+ ```js
65
+ export default eslintConfig({
66
+ convex: true,
67
+ unicorn: true,
68
+ rules: {
69
+ "unicorn/filename-case": "off", // Disables for all files including Convex
70
+ },
71
+ });
72
+ ```
73
+
27
74
  ## Learn More
28
75
 
29
76
  - [Convex ESLint Plugin](https://docs.convex.dev/eslint) - Official documentation
30
77
  - [Convex Validators](https://docs.convex.dev/functions/validation) - Argument validation guide
78
+ - [Unicorn Configuration](../unicorn/README.md) - Filename case options
31
79
  - [Main README](../../../README.md)
@@ -8,7 +8,7 @@ import { configNames } from "../constants.js";
8
8
  import { convexRules } from "./rules.js";
9
9
 
10
10
  // TODO: Replace with ESM import once @convex-dev/eslint-plugin stabilizes
11
- // The alpha version (0.0.1-alpha.4) doesn't have proper ESM exports yet
11
+ // The plugin doesn't have proper ESM exports yet
12
12
  const require = createRequire(import.meta.url);
13
13
 
14
14
  const convexPlugin = require("@convex-dev/eslint-plugin");
@@ -17,10 +17,12 @@ const convexPlugin = require("@convex-dev/eslint-plugin");
17
17
  * Creates an ESLint configuration for Convex.
18
18
  *
19
19
  * @param customRules - Optional object containing custom rules to override or add to the Convex configuration.
20
+ * @param unicorn - Whether the config uses unicorn rules.
20
21
  * @returns ESLint configuration object for Convex
21
22
  */
22
23
  export const convexConfig = (
23
24
  customRules?: Record<string, EslintRuleConfig>,
25
+ unicorn?: boolean,
24
26
  ): EslintConfigObject => ({
25
27
  files: ["**/convex/**/*.{ts,js}"],
26
28
  name: configNames.convex,
@@ -30,5 +32,16 @@ export const convexConfig = (
30
32
  rules: {
31
33
  ...convexRules,
32
34
  ...(customRules ?? {}),
35
+ // Convex files must be camelCase
36
+ ...(unicorn ?
37
+ {
38
+ "unicorn/filename-case": [
39
+ "warn",
40
+ {
41
+ case: "camelCase",
42
+ },
43
+ ],
44
+ }
45
+ : {}),
33
46
  },
34
47
  });
@@ -25,6 +25,7 @@ export const ignoresConfig = ({
25
25
  }): Linter.Config => ({
26
26
  ignores: [
27
27
  "**/dist/",
28
+ "**/build/",
28
29
  ...(reactFramework === "next" ? [".next"] : []),
29
30
  ...(reactFramework === "react-router" ? [".react-router"] : []),
30
31
  ...(storybook ? ["!.storybook"] : []),
@@ -64,7 +64,7 @@ export interface EslintConfigOptions {
64
64
  * @param options - The optional configuration object.
65
65
  * @param options.convex - Whether to include Convex rules.
66
66
  * @param options.functionStyle - The function style to enforce. Defaults to "arrow".
67
- * @param options.ignores - Additional paths to ignore. Already excludes `node_modules` and `dist`.
67
+ * @param options.ignores - Additional paths to ignore. Already excludes `node_modules`, `dist`, and `build`.
68
68
  * @param options.importPlugin - Whether to include the import plugin. Defaults to true.
69
69
  * @param options.jsdoc - Whether to include JSDoc rules. Set to false to disable, or provide an object to configure.
70
70
  * @param options.query - Whether to include TanStack Query rules.
@@ -126,11 +126,10 @@ export const eslintConfig = (
126
126
  ),
127
127
  ];
128
128
 
129
- if (jsdoc !== false) {
129
+ if (functionStyle === "arrow") {
130
130
  configs.push(
131
- jsdocConfig(
132
- jsdoc.requireJsdoc ?? false,
133
- categorizedRules[configNames.jsdoc],
131
+ preferArrowFunctionConfig(
132
+ categorizedRules[configNames.preferArrowFunction],
134
133
  ),
135
134
  );
136
135
  }
@@ -150,44 +149,29 @@ export const eslintConfig = (
150
149
  );
151
150
  }
152
151
 
153
- if (react) {
154
- const reactOptions = isObject(react) ? react : {};
155
-
156
- // Apply reactRefresh based on framework setting or explicit override
157
- const shouldUseReactRefresh =
158
- // Explicit setting takes precedence
159
- reactOptions.reactRefresh === true ||
160
- // Framework-based default (vite/none use reactRefresh by default)
161
- ((reactOptions.framework === "vite" ||
162
- reactOptions.framework === "none") &&
163
- reactOptions.reactRefresh !== false);
164
-
165
- if (shouldUseReactRefresh) {
166
- configs.push(
167
- reactRefreshEslintConfig(categorizedRules[configNames.reactRefresh]),
168
- );
169
- }
170
-
152
+ if (unicorn) {
153
+ const filenameCase = isObject(unicorn) ? unicorn.filenameCase : undefined;
171
154
  configs.push(
172
- reactEslintConfig({
173
- customRules: categorizedRules[configNames.react],
174
- functionStyle,
175
- reactCompiler: reactOptions.reactCompiler ?? true,
176
- typescript: Boolean(typescript),
155
+ unicornConfig({
156
+ customRules: categorizedRules[configNames.unicorn],
157
+ filenameCase,
177
158
  }),
178
159
  );
179
-
180
- if (isObject(react) && react.framework === "next") {
181
- configs.push(nextjsConfig(categorizedRules[configNames.nextjs]));
182
- }
183
160
  }
184
161
 
185
- if (query) {
186
- configs.push(queryConfig(categorizedRules[configNames.query]));
162
+ if (sorting) {
163
+ configs.push(
164
+ perfectionistConfig(categorizedRules[configNames.perfectionist]),
165
+ );
187
166
  }
188
167
 
189
- if (convex) {
190
- configs.push(convexConfig(categorizedRules[configNames.convex]));
168
+ if (jsdoc !== false) {
169
+ configs.push(
170
+ jsdocConfig(
171
+ jsdoc.requireJsdoc ?? false,
172
+ categorizedRules[configNames.jsdoc],
173
+ ),
174
+ );
191
175
  }
192
176
 
193
177
  if (testing !== false) {
@@ -232,27 +216,45 @@ export const eslintConfig = (
232
216
  );
233
217
  }
234
218
 
235
- if (sorting) {
236
- configs.push(
237
- perfectionistConfig(categorizedRules[configNames.perfectionist]),
238
- );
239
- }
219
+ if (react) {
220
+ const reactOptions = isObject(react) ? react : {};
221
+
222
+ // Apply reactRefresh based on framework setting or explicit override
223
+ const shouldUseReactRefresh =
224
+ // Explicit setting takes precedence
225
+ reactOptions.reactRefresh === true ||
226
+ // Framework-based default (vite/none use reactRefresh by default)
227
+ ((reactOptions.framework === "vite" ||
228
+ reactOptions.framework === "none") &&
229
+ reactOptions.reactRefresh !== false);
230
+
231
+ if (shouldUseReactRefresh) {
232
+ configs.push(
233
+ reactRefreshEslintConfig(categorizedRules[configNames.reactRefresh]),
234
+ );
235
+ }
240
236
 
241
- if (unicorn) {
242
- const filenameCase = isObject(unicorn) ? unicorn.filenameCase : undefined;
243
237
  configs.push(
244
- unicornConfig({
245
- customRules: categorizedRules[configNames.unicorn],
246
- filenameCase,
238
+ reactEslintConfig({
239
+ customRules: categorizedRules[configNames.react],
240
+ functionStyle,
241
+ reactCompiler: reactOptions.reactCompiler ?? true,
242
+ typescript: Boolean(typescript),
247
243
  }),
248
244
  );
245
+
246
+ if (isObject(react) && react.framework === "next") {
247
+ configs.push(nextjsConfig(categorizedRules[configNames.nextjs]));
248
+ }
249
249
  }
250
250
 
251
- if (functionStyle === "arrow") {
251
+ if (query) {
252
+ configs.push(queryConfig(categorizedRules[configNames.query]));
253
+ }
254
+
255
+ if (convex) {
252
256
  configs.push(
253
- preferArrowFunctionConfig(
254
- categorizedRules[configNames.preferArrowFunction],
255
- ),
257
+ convexConfig(categorizedRules[configNames.convex], Boolean(unicorn)),
256
258
  );
257
259
  }
258
260
 
@@ -110,6 +110,23 @@ export default eslintConfig({
110
110
  });
111
111
  ```
112
112
 
113
+ ### Special Case: Convex Integration
114
+
115
+ When both Unicorn and Convex configurations are enabled, Convex files automatically use **camelCase** regardless of your global filename case setting. This is because Convex files export functions that become API endpoints, and camelCase is the standard for function names.
116
+
117
+ ```js
118
+ export default eslintConfig({
119
+ convex: true,
120
+ unicorn: { filenameCase: "kebabCase" }, // Global setting
121
+ });
122
+
123
+ // Result:
124
+ // - Convex files (convex/**/*.{ts,js}): camelCase ✅ getUserData.ts
125
+ // - All other files: kebabCase ✅ user-service.ts
126
+ ```
127
+
128
+ [→ Learn more about Convex filename conventions](../convex/README.md#filename-convention)
129
+
113
130
  ### Node.js Protocol
114
131
 
115
132
  Requires using the `node:` protocol when importing Node.js built-in modules: