@nustackjs/lint 0.0.1 → 0.2.0

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/README.md CHANGED
@@ -68,6 +68,29 @@ eslint . # quick checks (fast)
68
68
  NUSTACK_LINT_DEPTH=full eslint . # + type-aware checks (CI)
69
69
  ```
70
70
 
71
+ ## What gets enabled
72
+
73
+ NuStack is an umbrella preset: it starts from
74
+ [`@antfu/eslint-config`](https://github.com/antfu/eslint-config), lets Nuxt generate the
75
+ project-aware `@nuxt/eslint` layer, then adds NuStack's own Nuxt ecosystem rules. The
76
+ exact flat config is still inspectable with the config inspector command below.
77
+
78
+ | Source | Enabled when | Plugins / rule namespaces | Enabled rules / presets |
79
+ | --- | --- | --- | --- |
80
+ | [`@antfu/eslint-config`](https://github.com/antfu/eslint-config) | Always, unless `base: false` | Core ESLint, TypeScript, Vue, import, node, unicorn, regexp, jsdoc, perfectionist, test/Vitest, pnpm, JSON/YAML/TOML/Markdown and other Antfu-detected slices | NuStack passes `stylistic: true`, `vue: true`, and keeps Antfu's normal defaults. Vitest rules are enabled as `test/*` in Antfu's renamed namespace. |
81
+ | `@nuxt/eslint` / `@nuxt/eslint-config` | Nuxt module setup | `nuxt/*`, Nuxt globals, Nuxt file-aware Vue handling | Official Nuxt rules such as `nuxt/prefer-import-meta`, `nuxt/no-page-meta-runtime-values`, and `nuxt/no-nuxt-config-test-key`; config key sorting follows Nuxt's own feature options. |
82
+ | `@nustack/nuxt` | Nuxt projects; `recommended` and above except the security floor | `@nustack/nuxt/*` | `@nustack/nuxt/no-secret-in-public-runtimeconfig` (`error`), `@nustack/nuxt/modules-order` (`error`), `@nustack/nuxt/no-deprecated-modules` (`error`), `@nustack/nuxt/no-explicit-auto-import` (`error`), `@nustack/nuxt/no-process-env` (`warn`) |
83
+ | NuStack Vue layer | Vue SFCs | `vue/*` | `vue/block-lang` (`error`); in `recommended` and above also `vue/define-emits-declaration`, `vue/define-props-destructuring`, `vue/html-comment-content-newline`, `vue/html-comment-indent`, `vue/no-duplicate-class-names`, `vue/no-empty-component-block`, `vue/no-import-compiler-macros` |
84
+ | `@nustack/vueuse` | App/client code; `recommended` and above | `@nustack/vueuse/*` | `@nustack/vueuse/no-nuxt-auto-import-collision`, `@nustack/vueuse/no-namespace-import`, `@nustack/vueuse/prefer-use-observers`, `@nustack/vueuse/prefer-use-storage`, `@nustack/vueuse/prefer-use-timers`, `@nustack/vueuse/prefer-useclipboard`, `@nustack/vueuse/prefer-useevent-listener`, `@nustack/vueuse/prefer-usewindow-size` |
85
+ | `@nustack/vite` | App/client code; `recommended` and above | `@nustack/vite/*` | `@nustack/vite/no-client-secret-pattern` (`error`), `@nustack/vite/no-public-src-import` (`warn`) |
86
+ | `eslint-plugin-better-tailwindcss` | Tailwind entry point detected, or `tailwind: true` | `better-tailwindcss/*` | `better-tailwindcss/enforce-consistent-class-order` (`warn`), `better-tailwindcss/no-unregistered-classes` (`warn`), `better-tailwindcss/no-conflicting-classes` (`error`), `better-tailwindcss/no-duplicate-classes` (`error`); `pedantic` also enables `better-tailwindcss/enforce-consistent-line-wrapping` |
87
+ | `@nustack/nuxt-ui` | `@nuxt/ui` detected, or `nuxtUi: true` | `@nustack/nuxt-ui/*` | `@nustack/nuxt-ui/prefer-u-button`, `@nustack/nuxt-ui/prefer-u-form-controls` |
88
+ | NuStack type-aware layer | `NUSTACK_LINT_DEPTH=full` | Antfu's renamed `ts/*` namespace | Enables TypeScript project service and `ts/no-deprecated` (`warn`) |
89
+
90
+ The public factory mirrors Antfu's override model: pass `base` to tune the Antfu layer,
91
+ pass per-concern options like `nuxt`, `vueUse`, or `tailwind` to tune NuStack concerns,
92
+ and use top-level `rules` for final global overrides.
93
+
71
94
  See [Configuration](./docs/configuration.md), [Rules](./docs/rules.md) and
72
95
  [Migration](./docs/migration.md).
73
96
 
package/dist/index.d.mts CHANGED
@@ -16,6 +16,8 @@ interface ConcernOptions {
16
16
  /** @deprecated Use `rules` instead. */
17
17
  overrides?: Rules;
18
18
  }
19
+ /** A per-concern toggle: `true`/`undefined` = default, `false` = off, object = tune. */
20
+ type ConcernToggle<T> = boolean | T;
19
21
 
20
22
  /** Options accepted by `@antfu/eslint-config`'s factory. */
21
23
  type AntfuOptions = NonNullable<Parameters<typeof antfu>[0]>;
@@ -23,8 +25,21 @@ type AntfuOptions = NonNullable<Parameters<typeof antfu>[0]>;
23
25
  interface NuxtConcernOptions extends ConcernOptions {
24
26
  }
25
27
 
28
+ /** Nuxt UI component-preference rules. */
26
29
  interface NuxtUiConcernOptions extends ConcernOptions {
27
30
  }
31
+ /**
32
+ * Per-module toggles for the Nuxt ecosystem concern. Each third-party Nuxt module
33
+ * gets its own toggle here — `true`/object enables (and tunes) it, `false` turns it
34
+ * off, and the default auto-gates it on detection. New ecosystem modules (Pinia,
35
+ * Content, …) are added as sibling toggles rather than as new `configs/` files.
36
+ */
37
+ interface NuxtEcosystemOptions {
38
+ /** Nuxt UI component preferences. Auto-gated on `@nuxt/ui` detection. */
39
+ nuxtUi?: ConcernToggle<NuxtUiConcernOptions>;
40
+ }
41
+ /** `false` disables the whole ecosystem; an object tunes each module in depth. */
42
+ type NuxtEcosystemToggle = ConcernToggle<NuxtEcosystemOptions>;
28
43
 
29
44
  interface TailwindConcernOptions extends ConcernOptions {
30
45
  /** Override the auto-detected Tailwind entry point (`@import "tailwindcss"`). */
@@ -49,8 +64,6 @@ interface VueUseConcernOptions extends ConcernOptions {
49
64
  type Awaitable<T> = T | Promise<T>;
50
65
  type NustackFlatConfig = Linter.Config | Linter.Config[] | FlatConfigComposer;
51
66
  type NustackUserConfig = Awaitable<NustackFlatConfig>;
52
- /** A per-concern toggle: `true`/`undefined` = default, `false` = off, object = tune. */
53
- type ConcernToggle<T> = boolean | T;
54
67
  /**
55
68
  * Options for the public nustack ESLint factory. Mirrors `@antfu/eslint-config`'s
56
69
  * shape: one options object, then optional flat config objects appended last.
@@ -69,8 +82,12 @@ interface NustackLintOptions {
69
82
  vueUse?: ConcernToggle<VueUseConcernOptions>;
70
83
  /** Vite build/runtime conventions. */
71
84
  vite?: ConcernToggle<ViteConcernOptions>;
72
- /** Nuxt UI component preferences. Auto-gated on `@nuxt/ui` detection. */
73
- nuxtUi?: ConcernToggle<NuxtUiConcernOptions>;
85
+ /**
86
+ * Nuxt-module ecosystem rules (Nuxt UI today; Pinia/Content later). `false`
87
+ * disables the whole ecosystem; an object tunes each module in depth, e.g.
88
+ * `{ nuxtUi: false }`. Each module auto-gates on its own detection.
89
+ */
90
+ nuxtEcosystem?: NuxtEcosystemToggle;
74
91
  /** Tailwind class sorting/correctness. Auto-gated on a detected entry point. */
75
92
  tailwind?: ConcernToggle<TailwindConcernOptions>;
76
93
  /** Global rule changes, merged after every concern. */
package/dist/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  import { defu } from 'defu';
2
2
  import { composer } from 'eslint-flat-config-utils';
3
3
  import antfu from '@antfu/eslint-config';
4
- import nuxtPlugin from '@nustackjs/lint-plugin-nuxt';
5
- import nuxtEcosystemPlugin from '@nustackjs/lint-plugin-nuxt-ecosystem';
4
+ import { nuxtConfigs } from '@nustackjs/lint-plugin-nuxt';
5
+ import { nuxtUiConfigs } from '@nustackjs/lint-plugin-nuxt-ecosystem';
6
6
  import betterTailwind from 'eslint-plugin-better-tailwindcss';
7
7
  import { getDefaultCallees, getDefaultAttributes } from 'eslint-plugin-better-tailwindcss/api/defaults';
8
8
  import { MatcherType } from 'eslint-plugin-better-tailwindcss/api/types';
9
- import vitePlugin from '@nustackjs/lint-plugin-vite';
10
- import vueUsePlugin from '@nustackjs/lint-plugin-vueuse';
9
+ import { viteConfigs } from '@nustackjs/lint-plugin-vite';
10
+ import { vueUseConfigs } from '@nustackjs/lint-plugin-vueuse';
11
11
 
12
12
  const ANTFU_DEFAULTS = {
13
13
  stylistic: true,
@@ -15,8 +15,10 @@ const ANTFU_DEFAULTS = {
15
15
  rules: {
16
16
  // Nuxt pages/layouts legitimately have multiple root nodes.
17
17
  "vue/no-multiple-template-root": "off",
18
- // `process` is a Nuxt/Nitro global; @nustack/nuxt/no-process-env handles app code.
19
- "node/prefer-global/process": "off",
18
+ // `process` is always available as a Nuxt/Nitro global, so prefer it over
19
+ // importing `node:process` (antfu's default is the opposite, `'never'`). App
20
+ // code is separately steered off `process.env` by @nustack/nuxt/no-process-env.
21
+ "node/prefer-global/process": ["error", "always"],
20
22
  "style/brace-style": ["warn", "1tbs"]
21
23
  }
22
24
  };
@@ -49,83 +51,31 @@ function resolveConcernRules(options) {
49
51
  ...options.rules
50
52
  };
51
53
  }
54
+ function isEnabled(toggle, gate) {
55
+ if (toggle === false)
56
+ return false;
57
+ if (toggle === true || typeof toggle === "object" && toggle !== null)
58
+ return true;
59
+ return gate;
60
+ }
61
+ function subOptions(toggle) {
62
+ return typeof toggle === "object" && toggle !== null ? toggle : {};
63
+ }
52
64
 
53
- const GLOB_APP$2 = ["**/*.vue", "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"];
54
- const GLOB_NUXT_CONFIG = ["**/nuxt.config.{ts,js,mjs,mts,cjs,cts}"];
55
- const IGNORE_NON_APP = [
56
- "**/server/**",
57
- "**/scripts/**",
58
- "**/packages/**",
59
- "**/*.{config,test,spec}.*",
60
- "**/*.d.ts"
61
- ];
62
- const recommendedRules$2 = nuxtPlugin.configs.recommended.rules ?? {};
63
65
  function nuxtConfig(ctx, axes, opts = {}) {
64
- const rules = resolveConcernRules(opts);
65
- const configs = [
66
- {
67
- // Secret leakage is a correctness/security floor — on at every variant.
68
- name: "nustack/nuxt/runtime-config",
69
- files: GLOB_NUXT_CONFIG,
70
- plugins: { "@nustack/nuxt": nuxtPlugin },
71
- rules: {
72
- "@nustack/nuxt/no-secret-in-public-runtimeconfig": "error"
73
- }
74
- }
75
- ];
76
- if (variantAtLeast(axes.variant, "recommended")) {
77
- configs.push(
78
- {
79
- name: "nustack/nuxt/auto-imports",
80
- files: GLOB_APP$2,
81
- ignores: IGNORE_NON_APP,
82
- plugins: { "@nustack/nuxt": nuxtPlugin },
83
- rules: {
84
- ...recommendedRules$2,
85
- "@nustack/nuxt/no-process-env": "off",
86
- "@nustack/nuxt/no-secret-in-public-runtimeconfig": "off",
87
- "@nustack/nuxt/no-explicit-auto-import": ["error", {
88
- imports: ctx.autoImports,
89
- components: ctx.components
90
- }]
91
- }
92
- },
93
- {
94
- name: "@nustack/nuxt/no-process-env",
95
- files: GLOB_APP$2,
96
- // process.env is legitimate in build/server config; only flag app code.
97
- ignores: IGNORE_NON_APP,
98
- plugins: { "@nustack/nuxt": nuxtPlugin },
99
- rules: {
100
- "@nustack/nuxt/no-process-env": recommendedRules$2["@nustack/nuxt/no-process-env"]
101
- }
102
- }
103
- );
104
- }
105
- if (Object.keys(rules).length) {
106
- configs.push({
107
- name: "nustack/nuxt/rules",
108
- files: GLOB_APP$2,
109
- rules
110
- });
111
- }
112
- return configs;
66
+ return nuxtConfigs({
67
+ variant: variantAtLeast(axes.variant, "recommended") ? "recommended" : "minimal",
68
+ autoImports: ctx.autoImports,
69
+ components: ctx.components,
70
+ rules: resolveConcernRules(opts)
71
+ });
113
72
  }
114
73
 
115
- const uiRules = nuxtEcosystemPlugin.configs.ui.rules ?? {};
116
- function nuxtUiConfig(_ctx, _axes, opts = {}) {
117
- const rules = resolveConcernRules(opts);
118
- return [
119
- {
120
- name: "nustack/nuxt-ui",
121
- files: ["**/*.vue"],
122
- plugins: { "@nustack/nuxt-ui": nuxtEcosystemPlugin },
123
- rules: {
124
- ...uiRules,
125
- ...rules
126
- }
127
- }
128
- ];
74
+ function nuxtEcosystemConfig(ctx, _axes, options = {}) {
75
+ const configs = [];
76
+ if (isEnabled(options.nuxtUi, ctx.modules.nuxtUi))
77
+ configs.push(...nuxtUiConfigs({ rules: resolveConcernRules(subOptions(options.nuxtUi)) }));
78
+ return configs;
129
79
  }
130
80
 
131
81
  const GLOB_CODE = ["**/*.vue", "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"];
@@ -180,36 +130,11 @@ function typeAwareConfig() {
180
130
  ];
181
131
  }
182
132
 
183
- const GLOB_APP$1 = ["**/*.vue", "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"];
184
- const IGNORE_NON_CLIENT$1 = [
185
- "**/server/**",
186
- "**/scripts/**",
187
- "**/packages/**",
188
- "**/*.{config,test,spec}.*",
189
- "**/*.d.ts"
190
- ];
191
- const recommendedRules$1 = vitePlugin.configs.recommended.rules ?? {};
192
133
  function viteConfig(axes, opts = {}) {
193
- const rules = resolveConcernRules(opts);
194
- const configs = [];
195
- if (variantAtLeast(axes.variant, "recommended")) {
196
- configs.push({
197
- name: "nustack/vite",
198
- files: GLOB_APP$1,
199
- ignores: IGNORE_NON_CLIENT$1,
200
- plugins: { "@nustack/vite": vitePlugin },
201
- rules: recommendedRules$1
202
- });
203
- }
204
- if (Object.keys(rules).length) {
205
- configs.push({
206
- name: "nustack/vite/rules",
207
- files: GLOB_APP$1,
208
- ignores: IGNORE_NON_CLIENT$1,
209
- rules
210
- });
211
- }
212
- return configs;
134
+ return viteConfigs({
135
+ variant: variantAtLeast(axes.variant, "recommended") ? "recommended" : "minimal",
136
+ rules: resolveConcernRules(opts)
137
+ });
213
138
  }
214
139
 
215
140
  function vueConfig(_ctx, _axes, opts = {}) {
@@ -239,36 +164,11 @@ function vueConfig(_ctx, _axes, opts = {}) {
239
164
  ];
240
165
  }
241
166
 
242
- const GLOB_APP = ["**/*.vue", "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"];
243
- const IGNORE_NON_CLIENT = [
244
- "**/server/**",
245
- "**/scripts/**",
246
- "**/packages/**",
247
- "**/*.{config,test,spec}.*",
248
- "**/*.d.ts"
249
- ];
250
- const recommendedRules = vueUsePlugin.configs.recommended.rules ?? {};
251
167
  function vueUseConfig(axes, opts = {}) {
252
- const rules = resolveConcernRules(opts);
253
- const configs = [];
254
- if (variantAtLeast(axes.variant, "recommended")) {
255
- configs.push({
256
- name: "nustack/vueuse",
257
- files: GLOB_APP,
258
- ignores: IGNORE_NON_CLIENT,
259
- plugins: { "@nustack/vueuse": vueUsePlugin },
260
- rules: recommendedRules
261
- });
262
- }
263
- if (Object.keys(rules).length) {
264
- configs.push({
265
- name: "nustack/vueuse/rules",
266
- files: GLOB_APP,
267
- ignores: IGNORE_NON_CLIENT,
268
- rules
269
- });
270
- }
271
- return configs;
168
+ return vueUseConfigs({
169
+ variant: variantAtLeast(axes.variant, "recommended") ? "recommended" : "minimal",
170
+ rules: resolveConcernRules(opts)
171
+ });
272
172
  }
273
173
 
274
174
  const EMPTY_CONTEXT = {
@@ -284,16 +184,6 @@ function resolveDepth() {
284
184
  function mergeContext(context) {
285
185
  return defu(context, EMPTY_CONTEXT);
286
186
  }
287
- function isEnabled(toggle, gate) {
288
- if (toggle === false)
289
- return false;
290
- if (toggle === true || typeof toggle === "object" && toggle !== null)
291
- return true;
292
- return gate;
293
- }
294
- function subOptions(toggle) {
295
- return typeof toggle === "object" && toggle !== null ? toggle : {};
296
- }
297
187
  function resolveRules(options) {
298
188
  return {
299
189
  ...options.overrides,
@@ -319,8 +209,8 @@ function applyNustackConfig(base, options = {}, ...userConfigs) {
319
209
  configs.push(...viteConfig(axes, subOptions(options.vite)));
320
210
  if (isEnabled(options.tailwind, ctx.tailwind.detected))
321
211
  configs.push(...tailwindConfig(ctx, axes, subOptions(options.tailwind)));
322
- if (isEnabled(options.nuxtUi, ctx.modules.nuxtUi))
323
- configs.push(...nuxtUiConfig(ctx, axes, subOptions(options.nuxtUi)));
212
+ if (options.nuxtEcosystem !== false)
213
+ configs.push(...nuxtEcosystemConfig(ctx, axes, subOptions(options.nuxtEcosystem)));
324
214
  if (depth === "full")
325
215
  configs.push(...typeAwareConfig());
326
216
  const rules = resolveRules(options);
@@ -335,7 +225,7 @@ function applyNustackConfig(base, options = {}, ...userConfigs) {
335
225
  function nustack(options = {}, ...userConfigs) {
336
226
  return applyNustackConfig(composer(), {
337
227
  nuxt: false,
338
- nuxtUi: false,
228
+ nuxtEcosystem: false,
339
229
  vite: false,
340
230
  vueUse: false,
341
231
  ...options,
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nustackjs/lint",
3
3
  "configKey": "@nustackjs/lint",
4
- "version": "0.0.1",
4
+ "version": "0.2.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nustackjs/lint",
3
3
  "type": "module",
4
- "version": "0.0.1",
4
+ "version": "0.2.0",
5
5
  "description": "Zero-config, project-aware ESLint (oxlint-ready) for Nuxt — plug it in and catch Nuxt bugs, not just style.",
6
6
  "author": "Norbiros <me@norbiros.dev>",
7
7
  "license": "MIT",
@@ -90,6 +90,7 @@
90
90
  "vue-tsc": "^3.3.3"
91
91
  },
92
92
  "scripts": {
93
+ "build": "nuxt-module-build prepare && nuxt-module-build build",
93
94
  "dev": "npm run dev:prepare && nuxt dev playground",
94
95
  "dev:build": "nuxt build playground",
95
96
  "dev:prepare": "pnpm --filter '@nustackjs/lint-plugin-*' build && nuxt-module-build build && nuxt-module-build prepare && nuxt prepare playground",