@scayle/eslint-config-storefront 4.8.3-alpha.1 → 5.1.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/CHANGELOG.md +117 -0
- package/README.md +126 -43
- package/config/ignores.mjs +46 -0
- package/config/index.mjs +129 -0
- package/config/javascript.mjs +159 -0
- package/config/rules.mjs +220 -0
- package/config/test.mjs +36 -0
- package/config/typescript.mjs +92 -0
- package/config/unicorn.mjs +33 -0
- package/config/v3/importResolver.mjs +71 -0
- package/config/v3/storybook.mjs +10 -0
- package/config/v3/tailwind.mjs +30 -0
- package/config/v3/vue.mjs +153 -0
- package/config/v3/vueComposable.mjs +29 -0
- package/config/v3/vueI18n.mjs +52 -0
- package/config/v3.mjs +66 -0
- package/config/v3.test.mjs +128 -0
- package/index.mjs +3 -2
- package/package.json +37 -17
- package/storefront-nuxt.mjs +4 -0
- package/storefront-v3.mjs +4 -0
- package/config/flat/importX.mjs +0 -22
- package/config/flat/index.mjs +0 -155
- package/config/flat/promise.mjs +0 -28
- package/config/flat/sonarjs.mjs +0 -19
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import tseslint from 'typescript-eslint'
|
|
2
|
+
import vueParser from 'vue-eslint-parser'
|
|
3
|
+
import pluginVue from 'eslint-plugin-vue'
|
|
4
|
+
import pluginVueA11y from 'eslint-plugin-vuejs-accessibility'
|
|
5
|
+
|
|
6
|
+
const GLOB_VUE = ['**/*.vue']
|
|
7
|
+
|
|
8
|
+
// Vue parser wiring so `.vue` files (including `<script setup lang="ts">`) parse correctly.
|
|
9
|
+
/** @returns {import('eslint').Linter.Config[]} Flat config entries wiring the Vue parser. */
|
|
10
|
+
function vueLanguageConfig() {
|
|
11
|
+
return [
|
|
12
|
+
...pluginVue.configs['flat/essential'],
|
|
13
|
+
...pluginVue.configs['flat/strongly-recommended'],
|
|
14
|
+
...pluginVue.configs['flat/recommended'],
|
|
15
|
+
{
|
|
16
|
+
name: 'storefront/v3/vue-language',
|
|
17
|
+
files: GLOB_VUE,
|
|
18
|
+
languageOptions: {
|
|
19
|
+
parser: vueParser,
|
|
20
|
+
parserOptions: {
|
|
21
|
+
ecmaVersion: 'latest',
|
|
22
|
+
parser: tseslint.parser,
|
|
23
|
+
sourceType: 'module',
|
|
24
|
+
extraFileExtensions: ['.vue'],
|
|
25
|
+
ecmaFeatures: { jsx: true },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {object} [config]
|
|
34
|
+
* @param {string[]} [config.undefComponentIgnores] Custom element tag names (e.g. web
|
|
35
|
+
* components) to exempt from `vue/no-undef-components`.
|
|
36
|
+
* @returns {import('eslint').Linter.Config[]} Flat config entries for Vue-specific rule overrides.
|
|
37
|
+
*/
|
|
38
|
+
function vueRulesConfig(config = {}) {
|
|
39
|
+
const { undefComponentIgnores = [] } = config
|
|
40
|
+
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
name: 'storefront/v3/vue-rules',
|
|
44
|
+
files: GLOB_VUE,
|
|
45
|
+
plugins: { vue: pluginVue },
|
|
46
|
+
rules: {
|
|
47
|
+
'eslint-comments/no-unlimited-disable': 'off',
|
|
48
|
+
// Core no-unused-vars isn't type-aware and misreads TypeScript-only
|
|
49
|
+
// constructs in <script setup lang="ts"> as real unused bindings.
|
|
50
|
+
// typescript.mjs only scopes the @typescript-eslint replacement to
|
|
51
|
+
// .ts/.tsx/.mts/.cts, so .vue files need their own registration.
|
|
52
|
+
'no-unused-vars': 'off',
|
|
53
|
+
'@typescript-eslint/no-unused-vars': [
|
|
54
|
+
'error',
|
|
55
|
+
{
|
|
56
|
+
args: 'all',
|
|
57
|
+
argsIgnorePattern: '^_',
|
|
58
|
+
caughtErrors: 'all',
|
|
59
|
+
caughtErrorsIgnorePattern: '^_',
|
|
60
|
+
destructuredArrayIgnorePattern: '^_',
|
|
61
|
+
varsIgnorePattern: '^_',
|
|
62
|
+
ignoreRestSiblings: true,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
'@typescript-eslint/no-empty-object-type': [
|
|
66
|
+
'error',
|
|
67
|
+
{ allowInterfaces: 'always' },
|
|
68
|
+
],
|
|
69
|
+
'vue/html-self-closing': [
|
|
70
|
+
'error',
|
|
71
|
+
{
|
|
72
|
+
html: { void: 'any', normal: 'any', component: 'always' },
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
'vue/v-bind-style': [
|
|
76
|
+
'error',
|
|
77
|
+
'shorthand',
|
|
78
|
+
{ sameNameShorthand: 'never' },
|
|
79
|
+
],
|
|
80
|
+
'vue/no-undef-components': [
|
|
81
|
+
'error',
|
|
82
|
+
{ ignorePatterns: undefComponentIgnores },
|
|
83
|
+
],
|
|
84
|
+
'vue/define-emits-declaration': ['error', 'type-literal'],
|
|
85
|
+
'vue/custom-event-name-casing': ['error', 'camelCase'],
|
|
86
|
+
'vue/slot-name-casing': ['error', 'kebab-case'],
|
|
87
|
+
'vue/require-default-prop': 'off',
|
|
88
|
+
'vue/multi-word-component-names': 'warn',
|
|
89
|
+
'vue/block-order': [
|
|
90
|
+
'error',
|
|
91
|
+
{ order: ['template', 'script', 'style'] },
|
|
92
|
+
],
|
|
93
|
+
// Vue Styling
|
|
94
|
+
// Based on https://github.com/nuxt/eslint/blob/main/packages/eslint-config/src/configs/vue.ts#L142
|
|
95
|
+
'vue/html-closing-bracket-newline': 'off',
|
|
96
|
+
'vue/html-closing-bracket-spacing': 'off',
|
|
97
|
+
'vue/html-indent': 'off',
|
|
98
|
+
'vue/html-quotes': 'off',
|
|
99
|
+
'vue/max-attributes-per-line': 'off',
|
|
100
|
+
'vue/multiline-html-element-content-newline': 'off',
|
|
101
|
+
'vue/mustache-interpolation-spacing': 'off',
|
|
102
|
+
'vue/no-multi-spaces': 'off',
|
|
103
|
+
'vue/no-spaces-around-equal-signs-in-attribute': 'off',
|
|
104
|
+
'vue/singleline-html-element-content-newline': 'off',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Vue accessibility. Every rule ships `off` by default: this preset's baseline reflects
|
|
111
|
+
// that a11y coverage is opt-in until a consumer's own audit turns rules on deliberately,
|
|
112
|
+
// not a statement that these rules don't matter.
|
|
113
|
+
/** @returns {import('eslint').Linter.Config[]} Flat config entries for Vue accessibility rules, defaulted off. */
|
|
114
|
+
function vueA11yConfig() {
|
|
115
|
+
return [
|
|
116
|
+
...pluginVueA11y.configs['flat/recommended'],
|
|
117
|
+
{
|
|
118
|
+
name: 'storefront/v3/vue-a11y',
|
|
119
|
+
files: GLOB_VUE,
|
|
120
|
+
rules: {
|
|
121
|
+
'vuejs-accessibility/mouse-events-have-key-events': 'off',
|
|
122
|
+
'vuejs-accessibility/no-static-element-interactions': 'off',
|
|
123
|
+
'vuejs-accessibility/anchor-has-content': 'off',
|
|
124
|
+
'vuejs-accessibility/aria-props': 'off',
|
|
125
|
+
'vuejs-accessibility/aria-role': 'off',
|
|
126
|
+
'vuejs-accessibility/aria-unsupported-elements': 'off',
|
|
127
|
+
'vuejs-accessibility/click-events-have-key-events': 'off',
|
|
128
|
+
'vuejs-accessibility/form-control-has-label': 'off',
|
|
129
|
+
'vuejs-accessibility/heading-has-content': 'off',
|
|
130
|
+
'vuejs-accessibility/iframe-has-title': 'off',
|
|
131
|
+
'vuejs-accessibility/interactive-supports-focus': 'off',
|
|
132
|
+
'vuejs-accessibility/label-has-for': 'off',
|
|
133
|
+
'vuejs-accessibility/media-has-caption': 'off',
|
|
134
|
+
'vuejs-accessibility/no-access-key': 'off',
|
|
135
|
+
'vuejs-accessibility/no-autofocus': 'off',
|
|
136
|
+
'vuejs-accessibility/no-distracting-elements': 'off',
|
|
137
|
+
'vuejs-accessibility/no-redundant-roles': 'off',
|
|
138
|
+
'vuejs-accessibility/role-has-required-aria-props': 'off',
|
|
139
|
+
'vuejs-accessibility/tabindex-no-positive': 'off',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @param {object} [config]
|
|
147
|
+
* @param {string[]} [config.undefComponentIgnores] Custom element tag names exempt from
|
|
148
|
+
* `vue/no-undef-components`.
|
|
149
|
+
* @returns {import('eslint').Linter.Config[]} Flat config entries for Vue language wiring, rules, and accessibility.
|
|
150
|
+
*/
|
|
151
|
+
export function vueConfig(config = {}) {
|
|
152
|
+
return [...vueLanguageConfig(), ...vueRulesConfig(config), ...vueA11yConfig()]
|
|
153
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import vueComposable from '@scayle/eslint-plugin-vue-composable'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {object} [config]
|
|
5
|
+
* @param {string[]} [config.fallthroughAttrsIgnores] Path fragments (e.g. `/src/client/pages/`)
|
|
6
|
+
* exempt from `require-explicit-fallthrough-attrs`, for components that never receive
|
|
7
|
+
* fallthrough attributes (page/layout roots mounted by a router).
|
|
8
|
+
* @returns {import('eslint').Linter.Config[]} Flat config entries for `@scayle/vue-composable` rules.
|
|
9
|
+
*/
|
|
10
|
+
export function vueComposableConfig(config = {}) {
|
|
11
|
+
const { fallthroughAttrsIgnores = [] } = config
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
name: 'storefront/v3/vue-composable',
|
|
16
|
+
plugins: { '@scayle/vue-composable': vueComposable },
|
|
17
|
+
rules: {
|
|
18
|
+
'@scayle/vue-composable/no-composable-after-await': 'error',
|
|
19
|
+
'@scayle/vue-composable/no-lifecycle-after-await': 'error',
|
|
20
|
+
'@scayle/vue-composable/no-watch-after-await': 'error',
|
|
21
|
+
'@scayle/vue-composable/no-computed-after-await': 'error',
|
|
22
|
+
'@scayle/vue-composable/require-explicit-fallthrough-attrs': [
|
|
23
|
+
'error',
|
|
24
|
+
{ ignores: fallthroughAttrsIgnores },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import vueI18n from '@intlify/eslint-plugin-vue-i18n'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {object} config
|
|
5
|
+
* @param {string} config.localeDir Glob pointing at the project's locale JSON files
|
|
6
|
+
* (e.g. `./src/i18n/locales/*.json`), passed to the `vue-i18n` plugin's `localeDir` setting.
|
|
7
|
+
* @param {string} [config.messageSyntaxVersion] `vue-i18n` message syntax version.
|
|
8
|
+
* @param {string[]} [config.unusedKeyIgnores] Regex-string patterns (e.g. `/foo.bar/`) for keys
|
|
9
|
+
* used dynamically in code, which `no-unused-keys` can't detect statically.
|
|
10
|
+
* @returns {import('eslint').Linter.Config[]} Flat config entries for `vue-i18n` linting.
|
|
11
|
+
*/
|
|
12
|
+
export function vueI18nConfig(config) {
|
|
13
|
+
const {
|
|
14
|
+
localeDir,
|
|
15
|
+
messageSyntaxVersion = '^11.2.8',
|
|
16
|
+
unusedKeyIgnores = [],
|
|
17
|
+
} = config ?? {}
|
|
18
|
+
|
|
19
|
+
if (!localeDir) {
|
|
20
|
+
throw new Error('storefrontV3: config.i18n.localeDir is required')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return [
|
|
24
|
+
...vueI18n.configs['flat/base'],
|
|
25
|
+
{
|
|
26
|
+
name: 'storefront/v3/vue-i18n',
|
|
27
|
+
rules: {
|
|
28
|
+
'@intlify/vue-i18n/valid-message-syntax': 'error',
|
|
29
|
+
// Passes when any of the locale files contains a used key
|
|
30
|
+
'@intlify/vue-i18n/no-missing-keys': 'error',
|
|
31
|
+
// Passes when all locale files contain the same keys.
|
|
32
|
+
'@intlify/vue-i18n/no-missing-keys-in-other-locales': process.env.CI
|
|
33
|
+
? 'error'
|
|
34
|
+
: 'warn',
|
|
35
|
+
'@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
|
|
36
|
+
'@intlify/vue-i18n/no-dynamic-keys': 'off',
|
|
37
|
+
// Performance-intensive: scans all matching files for unused translation keys.
|
|
38
|
+
'@intlify/vue-i18n/no-unused-keys': [
|
|
39
|
+
'error',
|
|
40
|
+
{
|
|
41
|
+
src: './src/{client,server,shared}/**',
|
|
42
|
+
extensions: ['.vue', '.ts'],
|
|
43
|
+
ignores: unusedKeyIgnores,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
settings: {
|
|
48
|
+
'vue-i18n': { localeDir, messageSyntaxVersion },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
}
|
package/config/v3.mjs
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { FlatConfigComposer } from 'eslint-flat-config-utils'
|
|
2
|
+
import { storefront } from './index.mjs'
|
|
3
|
+
import { vueConfig } from './v3/vue.mjs'
|
|
4
|
+
import { vueI18nConfig } from './v3/vueI18n.mjs'
|
|
5
|
+
import { vueComposableConfig } from './v3/vueComposable.mjs'
|
|
6
|
+
import { storybookConfig } from './v3/storybook.mjs'
|
|
7
|
+
import { tailwindConfig } from './v3/tailwind.mjs'
|
|
8
|
+
import {
|
|
9
|
+
importResolverConfig,
|
|
10
|
+
importOrderConfig,
|
|
11
|
+
} from './v3/importResolver.mjs'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {object} EslintConfigStorefrontV3Options
|
|
15
|
+
* @property {object} [typescript] Options forwarded to `typescriptConfig` (see `storefront`).
|
|
16
|
+
* @property {string} [typescript.tsconfigPath] Enables type-aware TypeScript rules when set.
|
|
17
|
+
* @property {object} [resolver] Import resolver settings.
|
|
18
|
+
* @property {string} [resolver.tsconfigPath] Absolute path to the tsconfig.json the import
|
|
19
|
+
* resolver reads `paths` aliases from. Independent of `typescript.tsconfigPath`: passing
|
|
20
|
+
* an absolute path here does not switch on type-aware TypeScript rules. Omit to fall back
|
|
21
|
+
* to `typescript.tsconfigPath`, then to the resolver's own `<cwd>/tsconfig.json` default,
|
|
22
|
+
* which only resolves correctly when ESLint's working directory happens to be this project's
|
|
23
|
+
* root (true for `pnpm lint`/CI via Turborepo, not guaranteed for an editor's ESLint
|
|
24
|
+
* extension). Set this explicitly, as an absolute path anchored to the config file's own
|
|
25
|
+
* location, if ESLint might ever run with a different working directory.
|
|
26
|
+
* @property {object} tailwind Tailwind CSS settings.
|
|
27
|
+
* @property {string} tailwind.entryPoint Path to the project's Tailwind CSS entry point.
|
|
28
|
+
* @property {object} i18n `vue-i18n` linting settings.
|
|
29
|
+
* @property {string} i18n.localeDir Glob pointing at the project's locale JSON files.
|
|
30
|
+
* @property {string} [i18n.messageSyntaxVersion] `vue-i18n` message syntax version.
|
|
31
|
+
* @property {string[]} [i18n.unusedKeyIgnores] Regex-string patterns for dynamically-used keys.
|
|
32
|
+
* @property {object} [vue] Vue rule settings.
|
|
33
|
+
* @property {string[]} [vue.undefComponentIgnores] Custom element tag names exempt from `vue/no-undef-components`.
|
|
34
|
+
* @property {object} [vueComposable] `@scayle/vue-composable` rule settings.
|
|
35
|
+
* @property {string[]} [vueComposable.fallthroughAttrsIgnores] Path fragments exempt from `require-explicit-fallthrough-attrs`.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds the SCAYLE Storefront ESLint flat config for the Storefront Application V3 stack
|
|
40
|
+
* (Hono + Inertia.js + Vue 3, not Nuxt). Extends `storefront()` with Vue language wiring,
|
|
41
|
+
* Vue rules, accessibility rules, `vue-i18n` rules, `@scayle/vue-composable` rules,
|
|
42
|
+
* Storybook file conventions, Tailwind CSS class validation, and a TypeScript-aware
|
|
43
|
+
* import resolver. Kept separate from `storefront()`/`storefrontNuxt()` since those two
|
|
44
|
+
* stay framework-agnostic.
|
|
45
|
+
*
|
|
46
|
+
* @param {EslintConfigStorefrontV3Options} config
|
|
47
|
+
* @returns {FlatConfigComposer<import('eslint').Linter.Config, never>} Returns a FlatConfigComposer object to be consumed in projects eslint.config.mjs files
|
|
48
|
+
*/
|
|
49
|
+
export const storefrontV3 = function (config) {
|
|
50
|
+
const { typescript, resolver, tailwind, i18n, vue, vueComposable } =
|
|
51
|
+
config ?? {}
|
|
52
|
+
|
|
53
|
+
/** @type {FlatConfigComposer<import('eslint').Linter.Config, never>} */
|
|
54
|
+
const flatConfigComposer = new FlatConfigComposer()
|
|
55
|
+
|
|
56
|
+
return flatConfigComposer.append(
|
|
57
|
+
storefront({ typescript }),
|
|
58
|
+
...tailwindConfig(tailwind),
|
|
59
|
+
...storybookConfig(),
|
|
60
|
+
...vueConfig(vue),
|
|
61
|
+
...vueI18nConfig(i18n),
|
|
62
|
+
...vueComposableConfig(vueComposable),
|
|
63
|
+
...importResolverConfig(resolver?.tsconfigPath ?? typescript?.tsconfigPath),
|
|
64
|
+
...importOrderConfig(),
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { storefrontV3 } from './v3.mjs'
|
|
3
|
+
|
|
4
|
+
describe('storefrontV3', () => {
|
|
5
|
+
it('throws without tailwind.entryPoint', () => {
|
|
6
|
+
expect(() =>
|
|
7
|
+
// @ts-expect-error missing required `tailwind`, exercising the runtime guard
|
|
8
|
+
storefrontV3({ i18n: { localeDir: './src/i18n/locales/*.json' } }),
|
|
9
|
+
).toThrow('config.tailwind.entryPoint is required')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('throws without i18n.localeDir', () => {
|
|
13
|
+
expect(() =>
|
|
14
|
+
// @ts-expect-error missing required `i18n`, exercising the runtime guard
|
|
15
|
+
storefrontV3({ tailwind: { entryPoint: './src/client/index.css' } }),
|
|
16
|
+
).toThrow('config.i18n.localeDir is required')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('returns a config with both required options set', async () => {
|
|
20
|
+
const config = storefrontV3({
|
|
21
|
+
tailwind: { entryPoint: './src/client/index.css' },
|
|
22
|
+
i18n: { localeDir: './src/i18n/locales/*.json' },
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const resolved = await config.toConfigs()
|
|
26
|
+
|
|
27
|
+
expect(Array.isArray(resolved)).toBe(true)
|
|
28
|
+
expect(resolved.length).toBeGreaterThan(0)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// These settings have no test-visible failure mode of their own: a reverted or
|
|
32
|
+
// misconfigured resolver/extensions/groups entry still produces a config that loads
|
|
33
|
+
// and lints without error, it just silently stops catching what it's meant to catch.
|
|
34
|
+
// Asserting the shape here is what makes a quiet revert fail CI instead of passing it.
|
|
35
|
+
it('wires the import resolver and no-cycle extensions', async () => {
|
|
36
|
+
const config = storefrontV3({
|
|
37
|
+
tailwind: { entryPoint: './src/client/index.css' },
|
|
38
|
+
i18n: { localeDir: './src/i18n/locales/*.json' },
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const resolved = await config.toConfigs()
|
|
42
|
+
const resolverEntry = resolved.find(
|
|
43
|
+
(c) => c.name === 'storefront/v3/import-resolver',
|
|
44
|
+
)
|
|
45
|
+
if (!resolverEntry) {
|
|
46
|
+
throw new Error('resolverEntry not found')
|
|
47
|
+
}
|
|
48
|
+
const { settings } = resolverEntry
|
|
49
|
+
if (!settings) {
|
|
50
|
+
throw new Error('resolverEntry.settings not found')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const resolverNext =
|
|
54
|
+
/** @type {{ name: string, interfaceVersion: number }[]} */ (
|
|
55
|
+
settings['import-x/resolver-next']
|
|
56
|
+
)
|
|
57
|
+
expect(resolverNext).toHaveLength(1)
|
|
58
|
+
expect(resolverNext[0]).toMatchObject({
|
|
59
|
+
name: 'eslint-import-resolver-typescript',
|
|
60
|
+
interfaceVersion: 3,
|
|
61
|
+
})
|
|
62
|
+
expect(settings['import-x/extensions']).toEqual([
|
|
63
|
+
'.js',
|
|
64
|
+
'.mjs',
|
|
65
|
+
'.cjs',
|
|
66
|
+
'.ts',
|
|
67
|
+
'.tsx',
|
|
68
|
+
])
|
|
69
|
+
expect(settings['import-x/extensions']).not.toContain('.vue')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('ranks internal imports alongside external ones in import-x/order', async () => {
|
|
73
|
+
const config = storefrontV3({
|
|
74
|
+
tailwind: { entryPoint: './src/client/index.css' },
|
|
75
|
+
i18n: { localeDir: './src/i18n/locales/*.json' },
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const resolved = await config.toConfigs()
|
|
79
|
+
const orderEntry = resolved.find(
|
|
80
|
+
(c) => c.name === 'storefront/v3/import-order',
|
|
81
|
+
)
|
|
82
|
+
if (!orderEntry) {
|
|
83
|
+
throw new Error('orderEntry not found')
|
|
84
|
+
}
|
|
85
|
+
const { rules } = orderEntry
|
|
86
|
+
if (!rules) {
|
|
87
|
+
throw new Error('orderEntry.rules not found')
|
|
88
|
+
}
|
|
89
|
+
const orderRule = rules['import-x/order']
|
|
90
|
+
if (!orderRule) {
|
|
91
|
+
throw new Error('import-x/order rule not found')
|
|
92
|
+
}
|
|
93
|
+
const [severity, options] = /** @type {[string, { groups: unknown[] }]} */ (
|
|
94
|
+
orderRule
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
expect(severity).toBe('error')
|
|
98
|
+
expect(options.groups).toEqual([
|
|
99
|
+
'builtin',
|
|
100
|
+
['external', 'internal'],
|
|
101
|
+
'parent',
|
|
102
|
+
'sibling',
|
|
103
|
+
'index',
|
|
104
|
+
])
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('accepts resolver.tsconfigPath independently of typescript.tsconfigPath', async () => {
|
|
108
|
+
// Both must resolve without throwing: resolver.tsconfigPath alone (no type-aware
|
|
109
|
+
// TypeScript rules switched on) and typescript.tsconfigPath alone (the fallback
|
|
110
|
+
// path, still feeding the resolver). The resolver's own internals aren't asserted
|
|
111
|
+
// on here, only that both option shapes are accepted.
|
|
112
|
+
await expect(
|
|
113
|
+
storefrontV3({
|
|
114
|
+
tailwind: { entryPoint: './src/client/index.css' },
|
|
115
|
+
i18n: { localeDir: './src/i18n/locales/*.json' },
|
|
116
|
+
resolver: { tsconfigPath: '/absolute/resolver-tsconfig.json' },
|
|
117
|
+
}).toConfigs(),
|
|
118
|
+
).resolves.toBeInstanceOf(Array)
|
|
119
|
+
|
|
120
|
+
await expect(
|
|
121
|
+
storefrontV3({
|
|
122
|
+
tailwind: { entryPoint: './src/client/index.css' },
|
|
123
|
+
i18n: { localeDir: './src/i18n/locales/*.json' },
|
|
124
|
+
typescript: { tsconfigPath: '/absolute/typescript-tsconfig.json' },
|
|
125
|
+
}).toConfigs(),
|
|
126
|
+
).resolves.toBeInstanceOf(Array)
|
|
127
|
+
})
|
|
128
|
+
})
|
package/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scayle/eslint-config-storefront",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "SCAYLE Commerce Engine specific eslint configuration",
|
|
5
5
|
"author": "SCAYLE Commerce Engine",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,46 +17,66 @@
|
|
|
17
17
|
"type": "module",
|
|
18
18
|
"exports": {
|
|
19
19
|
".": "./index.mjs",
|
|
20
|
-
"./
|
|
21
|
-
"./
|
|
22
|
-
"./config/flat/promise.mjs": "./config/flat/promise.mjs",
|
|
23
|
-
"./config/flat/importX.mjs": "./config/flat/importX.mjs"
|
|
20
|
+
"./nuxt": "./storefront-nuxt.mjs",
|
|
21
|
+
"./v3": "./storefront-v3.mjs"
|
|
24
22
|
},
|
|
25
23
|
"main": "./index.mjs",
|
|
26
24
|
"files": [
|
|
27
25
|
"CHANGELOG.md",
|
|
28
26
|
"README.md",
|
|
29
27
|
"./index.mjs",
|
|
28
|
+
"./storefront-nuxt.mjs",
|
|
29
|
+
"./storefront-v3.mjs",
|
|
30
30
|
"./config/"
|
|
31
31
|
],
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">= 22.0.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"eslint": ">=
|
|
36
|
+
"eslint": ">=10.4.0",
|
|
37
37
|
"typescript": ">=5.4.0"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@
|
|
41
|
-
"@eslint/
|
|
42
|
-
"eslint
|
|
40
|
+
"@e18e/eslint-plugin": "^0.8.0",
|
|
41
|
+
"@eslint-community/eslint-plugin-eslint-comments": "^4.7.2",
|
|
42
|
+
"@eslint/js": "^10.0.0",
|
|
43
|
+
"@intlify/eslint-plugin-vue-i18n": "^4.5.1",
|
|
44
|
+
"@scayle/eslint-plugin-vue-composable": "1.2.1",
|
|
45
|
+
"@vitest/eslint-plugin": "^1.6.20",
|
|
46
|
+
"eslint-config-flat-gitignore": "^2.3.0",
|
|
43
47
|
"eslint-flat-config-utils": "^3.2.0",
|
|
44
|
-
"eslint-
|
|
48
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
49
|
+
"eslint-plugin-better-tailwindcss": "^4.7.0",
|
|
50
|
+
"eslint-plugin-import-x": "^4.17.1",
|
|
51
|
+
"eslint-plugin-jsdoc": "^64.2.1",
|
|
52
|
+
"eslint-plugin-jsonc": "^3.4.1",
|
|
53
|
+
"eslint-plugin-n": "^18.3.0",
|
|
54
|
+
"eslint-plugin-no-only-tests": "^3.4.0",
|
|
45
55
|
"eslint-plugin-promise": "^7.3.0",
|
|
46
|
-
"eslint-plugin-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
56
|
+
"eslint-plugin-regexp": "^3.2.0",
|
|
57
|
+
"eslint-plugin-sonarjs": "^4.2.0",
|
|
58
|
+
"eslint-plugin-storybook": "^10.5.8",
|
|
59
|
+
"eslint-plugin-unicorn": "^73.0.0",
|
|
60
|
+
"eslint-plugin-vue": "^10.10.0",
|
|
61
|
+
"eslint-plugin-vuejs-accessibility": "^2.6.0",
|
|
62
|
+
"globals": "^17.7.0",
|
|
63
|
+
"typescript-eslint": "^8.67.0",
|
|
64
|
+
"vue-eslint-parser": "^10.4.1"
|
|
49
65
|
},
|
|
50
66
|
"devDependencies": {
|
|
51
|
-
"
|
|
67
|
+
"@types/node": "^24",
|
|
68
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
69
|
+
"eslint": "10.9.1",
|
|
52
70
|
"eslint-formatter-gitlab": "7.2.0",
|
|
53
|
-
"publint": "0.3.
|
|
54
|
-
"typescript": "6.0.3"
|
|
71
|
+
"publint": "0.3.24",
|
|
72
|
+
"typescript": "6.0.3",
|
|
73
|
+
"vitest": "4.1.11"
|
|
55
74
|
},
|
|
56
75
|
"scripts": {
|
|
57
76
|
"lint": "eslint .",
|
|
58
77
|
"lint:ci": "eslint . --format gitlab",
|
|
59
78
|
"lint:fix": "eslint . --fix",
|
|
60
|
-
"package:lint": "publint"
|
|
79
|
+
"package:lint": "publint",
|
|
80
|
+
"typecheck": "tsc"
|
|
61
81
|
}
|
|
62
82
|
}
|
package/config/flat/importX.mjs
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { importX as pluginImportX } from 'eslint-plugin-import-x'
|
|
2
|
-
|
|
3
|
-
// Pre-defined rule configurations for better performance
|
|
4
|
-
const IMPORT_X_RULES = {
|
|
5
|
-
'import-x/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
|
6
|
-
'import-x/first': 'error',
|
|
7
|
-
'import-x/no-duplicates': 'error',
|
|
8
|
-
'import-x/no-mutable-exports': 'error',
|
|
9
|
-
'import-x/no-named-default': 'error',
|
|
10
|
-
'import-x/no-self-import': 'error',
|
|
11
|
-
'import-x/no-extraneous-dependencies': 'error',
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function importX() {
|
|
15
|
-
return [
|
|
16
|
-
{
|
|
17
|
-
name: 'storefront/import-x/rules',
|
|
18
|
-
plugins: { 'import-x': pluginImportX },
|
|
19
|
-
rules: IMPORT_X_RULES,
|
|
20
|
-
},
|
|
21
|
-
]
|
|
22
|
-
}
|