@scayle/eslint-config-storefront
| Install | |
|---|---|
npm install @scayle/eslint-config-storefront |
|
| Latest Version: | 5.3.1 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Oct 19, 2026 |
| Links: | npm |
@scayle/eslint-config-storefront
A shared eslint config for SCAYLE Storefront projects and packages.
Provides SCAYLE-specific eslint configuration for Frontend and Node.js projects using TypeScript.
Flat config only (ESLint 10.4+). Built directly on first-class plugins (@eslint/js, typescript-eslint, eslint-plugin-n, eslint-plugin-jsonc, eslint-plugin-jsdoc, eslint-plugin-unicorn, eslint-plugin-regexp, @e18e/eslint-plugin, and others) with no shared-config aggregator dependency.
Should you require support for eslint@8, please use @scayle/eslint-config-storefront@3.
Entry points
The package exposes three entry points, all built on the same shared rule set. Pick the one that matches your stack:
| Entry point | Export | For | Adds on top of the shared rules |
|---|---|---|---|
| Default | @scayle/eslint-config-storefront |
Framework-agnostic TypeScript/Node.js packages | JavaScript, TypeScript, JSDoc, and import rules |
| Nuxt | @scayle/eslint-config-storefront/nuxt |
Nuxt projects — this is what Storefront Application V2 uses, since V2 is a Nuxt 4 application | Nothing beyond the shared rules; @nuxt/eslint already supplies JavaScript, TypeScript, JSDoc, and import rules |
| V3 | @scayle/eslint-config-storefront/v3 |
The Storefront Application V3 stack (Hono + Inertia.js + Vue 3, not Nuxt) | Everything the default entry point adds, plus Vue language wiring, Vue rules, accessibility rules, vue-i18n linting, @scayle/vue-composable rules, Storybook file conventions, Tailwind CSS class validation, a TypeScript-aware import resolver, and the require-lazy-promise-in-view-props rule |
There is no /v2 entry point: Storefront Application V2 is a Nuxt application, so it uses the
/nuxt entry point directly, the same as any other Nuxt project. "V2" and "V3" describe the two
Storefront Application stacks this monorepo ships, not two separate ESLint configs — V2 maps to
/nuxt, V3 maps to /v3.
Internally, /nuxt and /v3 both build on the same base config, so a rule fixed or added to the
shared set (JavaScript, Node.js, JSON/JSONC, test files, SonarJS, Promise usage, modernization,
regex safety, comments hygiene) applies to every entry point without duplicating configuration.
Only /v3 and the default entry point add TypeScript, JSDoc, and import rules directly — /nuxt
relies on @nuxt/eslint for those instead.
Install
Add the @scayle/eslint-config-storefront manually to your project:
pnpm i -D eslint @scayle/eslint-config-storefront
yarn add -D eslint @scayle/eslint-config-storefront
And create a eslint.config.mjs in your project root:
// eslint.config.mjs
import storefront from '@scayle/eslint-config-storefront'
export default storefront()
Nuxt projects (Storefront Application V2)
Nuxt projects already get JavaScript, TypeScript, JSDoc, and import rules from
@nuxt/eslint. Use the /nuxt entry point instead, which
only adds the framework-agnostic rules (comments hygiene, Node.js, JSON/JSONC, test-file
conventions, SonarJS, Promise usage, modernization, and regex safety):
// eslint.config.mjs
import storefrontNuxt from '@scayle/eslint-config-storefront/nuxt'
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt(storefrontNuxt())
This is the entry point Storefront Application V2's own eslint.config.mjs uses.
Storefront Application V3 projects
The Storefront Application V3 stack (Hono + Inertia.js + Vue 3, not Nuxt) should use the
/v3 entry point. It builds on the default config and adds Vue language wiring, Vue rules,
accessibility rules, vue-i18n linting, @scayle/vue-composable rules, Storybook file
conventions, Tailwind CSS class validation, a TypeScript-aware import resolver, and the
require-lazy-promise-in-view-props rule:
// eslint.config.mjs
import storefrontV3 from '@scayle/eslint-config-storefront/v3'
export default storefrontV3({
tailwind: {
entryPoint: './src/client/index.css',
},
i18n: {
localeDir: './src/i18n/locales/*.json',
},
})
tailwind.entryPoint and i18n.localeDir are required, the underlying plugins can't
validate Tailwind classes or i18n keys without a real path. Optional options:
typescript.tsconfigPath: absolute path to atsconfig.json. Enables type-aware TypeScript rules and points the import resolver at that file instead of<root>/tsconfig.jsonresolver.tsconfigPath: absolute path to atsconfig.jsonfor the import resolver and, with the same?? typescript.tsconfigPathfallback, for type-checking therequire-lazy-promise-in-view-propsrule below — independent oftypescript.tsconfigPathin the sense that setting it does not switch on@typescript-eslint/*type-checked rules (recommendedTypeChecked) generally. Use this instead oftypescript.tsconfigPathwhen you want the resolver fix and the lazy-promise rule without also switching on@typescript-eslint/*type-checked rules project-wide. When neither option resolves a tsconfig path,require-lazy-promise-in-view-propsis omitted entirely rather than registered without type information. Setting it DOES have one other real effect: because ESLint flat config merges type information across every config block matching a file, and the lazy-promise rule's own block matches every.ts/.tsx/.mts/.ctsfile (not just files with agetViewPropsmethod),eslint-plugin-sonarjs's type-aware rules gain real type information project-wide too — they silently report nothing without a type checker, so expect newsonarjsfindings the first time you set this option, not just wherever the lazy-promise rule itself looksi18n.messageSyntaxVersion:vue-i18nmessage syntax version, defaults to^11.2.8i18n.unusedKeyIgnores: regex-string patterns for translation keys used dynamically in codevue.undefComponentIgnores: custom element (web component) tag names to exempt fromvue/no-undef-componentsvueComposable.fallthroughAttrsIgnores: path fragments to exempt fromrequire-explicit-fallthrough-attrs(e.g. router-mounted page/layout roots that never receive fallthrough attributes)
This is the entry point Storefront Application V3's own eslint.config.mjs uses.
Vue rules and accessibility
/v3 wires the Vue parser (including <script setup lang="ts">) via vue-eslint-parser, applies
eslint-plugin-vue's essential, strongly-recommended, and recommended presets, and layers a
handful of Storefront-specific overrides on top (e.g. vue/custom-event-name-casing,
vue/block-order, styling rules disabled where they conflict with this project's Prettier
config). vue-eslint-parser doesn't reuse the type-aware @typescript-eslint/no-unused-vars
config from the TypeScript rules, since that one only scopes to .ts/.tsx/.mts/.cts files,
so .vue files get their own registration of the same rule.
Vue accessibility rules (eslint-plugin-vuejs-accessibility's recommended preset) are wired but
every rule ships off by default. That baseline reflects that a11y coverage is opt-in until a
consuming project runs its own audit and turns rules on deliberately — it is not a statement that
accessibility doesn't matter.
vue-i18n linting
Validates message syntax, catches missing/unused/duplicate translation keys, and — only in
CI (process.env.CI set) — enforces that every locale file carries the same keys as the others
at error (a local warn lets you author a key in one locale before running the translation
step). Requires i18n.localeDir to be set; there is no default, since the plugin can't validate
anything without a real path to your locale files.
@scayle/vue-composable rules
Enables all five rules from the sibling
@scayle/eslint-plugin-vue-composable package at error:
no-composable-after-await, no-lifecycle-after-await, no-watch-after-await,
no-computed-after-await (composables/lifecycle hooks/watchers/computed refs called after an
await inside a composable bind to the wrong instance), and
require-explicit-fallthrough-attrs (multi-root components must declare inheritAttrs: false,
since Vue can't infer where fallthrough attributes should land). See that package's own README
for the full rationale and examples per rule.
Storybook conventions
Applies eslint-plugin-storybook's recommended preset to files matching *.stories.* /
*.story.*. No configuration needed.
Tailwind CSS class validation
Validates Tailwind utility classes via eslint-plugin-better-tailwindcss's recommended preset,
against the CSS entry point you provide in tailwind.entryPoint. Class-order and
line-wrapping rules are disabled, since this project's Prettier Tailwind plugin already handles
sorting and would otherwise conflict with the ESLint plugin's own ordering opinion.
no-deprecated-classes is disabled too: the /v3 entry point targets Tailwind v4, so this
rule's v3 class-name findings don't apply.
Import resolution
/v3 wires eslint-import-resolver-typescript, so import-x rules resolve tsconfig.json
path aliases (@client/*, @server/*, or any other paths entry) to their real files
instead of only matching literal relative imports. Three rules depend on that resolution
to work correctly against an aliased import:
import-x/no-restricted-paths: zone violations across an alias fail lint, not just across a relative pathimport-x/no-cycle: circular dependencies through an aliased import are caught..vuefiles are the one exception, a cycle through a.vuefile only surfaces when ESLint lints that file directlyimport-x/order: aliased imports rank alongside package imports (external), ahead of relative ones, instead of dropping to last place once the resolver can see they resolve to a project file
Without resolver.tsconfigPath or typescript.tsconfigPath set, both the resolver and
import-x/no-restricted-paths's zones resolve relative to ESLint's own working directory.
That's correct for pnpm lint and CI, since a monorepo's task runner always runs a
package's lint script with that package as the working directory, but not guaranteed for
an editor's ESLint extension, which can run with the workspace root as its working
directory instead. Set resolver.tsconfigPath to an absolute path (for example,
new URL('./tsconfig.json', import.meta.url).pathname in your eslint.config.mjs) if your
editor's linting needs to match the command-line result.
require-lazy-promise-in-view-props
Enforces the lazyPromise convention in Inertia controllers: a promise created inside a
controller's getViewProps() method must be wrapped in the SDK's lazyPromise(() => ...)
helper, unless it's awaited or returned in the same statement it's created in. This closes
two related problems that a promise created eagerly in getViewProps() can cause: a
partial reload that excludes every prop consuming that promise still triggers the fetch
(wasted work), and an uncaught rejection on such a promise can surface as an unhandled
promise rejection. The rule reports at error and offers a suggestion (not an autofix,
since call sites also need to change from a bare reference to an accessor call, x → x())
that wraps the flagged expression.
// Before — flagged
const productsPromise = fetchProducts(params)
return {
products: async () => (await productsPromise).items,
pagination: async () => (await productsPromise).pagination,
}
// After — the suggested fix, plus updating both call sites
const getProducts = lazyPromise(() => fetchProducts(params))
return {
products: async () => (await getProducts()).items,
pagination: async () => (await getProducts()).pagination,
}
The rule is type-aware: it detects any thenable-typed local (not just a literal Promise
name match), so a PromiseLike-shaped or branded return type is caught too. It only checks
locals declared directly in getViewProps()'s own body — a promise created and consumed
entirely inside a nested function (a prop callback, a nested class's method) is not flagged,
since that promise is already effectively lazy: the enclosing function only runs when it's
invoked. A correctly-wrapped lazyPromise(() => ...) call itself returns a function, not a
thenable, so it never trips the check; passing lazyPromise an already-invoked promise
instead of a factory (lazyPromise(fetchThing()) instead of
lazyPromise(() => fetchThing())) is a type error the TypeScript compiler catches on its
own — the wrong argument shape doesn't satisfy lazyPromise's signature — so the rule
doesn't need its own check for that case.
Known limitations: the rule does not verify that the lazyPromise identifier it sees
resolves to the SDK's own export — a same-named local declaration would also satisfy it. It
likewise doesn't verify that a consuming prop callback actually chains the accessor's result
into its own return value; a callback that calls the accessor without awaiting or returning
it could still produce an unhandled rejection the rule won't catch. It also doesn't track
references across sibling declarators in the same body, so two independent promises
consumed together via Promise.all([a, b]) are each flagged individually even though
neither needs lazyPromise there.
Requires resolver.tsconfigPath or typescript.tsconfigPath to be set — see
Import resolution above. When neither is set, the rule is omitted
entirely from the config rather than registered without type information, which would
otherwise crash ESLint on the first .ts file it lints. Note that resolver.tsconfigPath
is passed to parserOptions.projectService.defaultProject, which is a fallback for files
not covered by an auto-discovered tsconfig.json — and which typescript-eslint hard-fails
on after 8 unmatched files by design. If your project's own tsconfig.json doesn't cover
every .ts file this rule lints, you may see an ESLint crash rather than a lint error.
Add script for package.json
For example:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}
VS Code support (auto fix on save)
Install VS Code ESLint extension
Add the following settings to your .vscode/settings.json:
{
// Enable the ESlint flat config support
"eslint.experimental.useFlatConfig": true,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never",
},
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
],
}
What the shared rules cover
Every entry point (default, /nuxt, /v3) includes the following rule domains. They live in
config/rules.mjs, config/javascript.mjs, config/typescript.mjs, config/unicorn.mjs,
config/test.mjs, and config/ignores.mjs in this package's source, one factory function per
domain:
- Comments hygiene (
eslint-comments/*): no aggregating/duplicate/unlimitedeslint-disablecomments, no unusedeslint-enable. - Node.js (
node/*): deprecated API usage, callback error-handling conventions,process/Bufferglobals preferred over the bare identifiers. - JSON/JSONC (
jsonc/*): structural validity plus formatting rules for.json,.json5, and.jsoncfiles. - Test files (
vitest/*,no-only-tests/*):it/describeconventions, no duplicate test titles, no accidental.onlyleft in committed code (errorin CI,warnlocally so you can use.onlywhile debugging). - SonarJS (
sonarjs/*): the plugin's recommended preset, withno-duplicate-stringoff,todo-tag/fixme-tagdowngraded towarn, andaws-restricted-ip-admin-accessoff (it only fires on AWS CDK infrastructure code). - Promise usage (
promise/*): a curated subset ofeslint-plugin-promise's rules —no-multiple-resolved,no-new-statics, andno-return-wrapaterror, the rest atwarn.catch-or-returnandalways-returnonly inspect a promise used as a bare statement, so a project whose promises are all stored in a variable before being awaited or returned (as V3's controller code is) can safely turn bothoffin its owneslint.config.mjs— Storefront Application V3's own config does exactly that. (require-lazy-promise-in-view-propsis what enforces the equivalent convention for V3 controllers; see above.) - Modernization (
unicorn/*,e18e/*): a curated subset ofeslint-plugin-unicorn(not its fullrecommendedpreset, which is hundreds of largely stylistic rules), plus the e18e community's rule set for modernization, module-replacement, and performance findings. Everye18e/*rule ships atwarn— the plugin is still pre-1.0 and its rule set hasn't been reviewed against every consuming project yet, so a warning surfaces a finding without failing your build. Seee18e/*rules ship as warnings below for the one rule worth knowing about specifically. - Regex safety (
regexp/*):eslint-plugin-regexp's recommended preset, including ReDoS-prone pattern detection.
The default and /v3 entry points additionally include:
- JavaScript (
@eslint/js's recommended preset plus Storefront-specific additions): strict equality, no implicit globals, consistentconst/letusage, and similar correctness rules that apply regardless of TypeScript. - TypeScript (
@typescript-eslint/*): the plugin's recommended preset, upgraded torecommendedTypeCheckedwhen atsconfigPathis provided (see Storefront Application V3 projects above for how V3 supplies one), plus Storefront-specific overrides — consistent type-only imports,interfaceovertypefor object shapes, unused-variable detection that allows a leading underscore to mark an intentionally-unused binding. - JSDoc (
jsdoc/*): validates JSDoc comment structure (parameter names, return descriptions, alignment) atwarn. This package is published and consumed by external developers, so exported symbols are expected to carry meaningful documentation. - Import correctness (
import-x/*): duplicate imports, mutable exports, self-imports, extraneous dependencies not declared inpackage.json, and consistent type-import specifier style. - Ignore patterns: build artifacts, lockfiles, and tooling caches (
node_modules,dist,.output, coverage reports, and similar) are excluded, on top of whatever your project's own.gitignoreexcludes.
/nuxt omits all five of the above: @nuxt/eslint already provides JavaScript, TypeScript,
JSDoc, and import rules for a Nuxt project, and Nuxt's own tooling manages its build-output
ignore patterns.
Customization
Default config / @scayle/eslint-config-storefront
// eslint.config.mjs
import storefront from '@scayle/eslint-config-storefront'
export default storefront({
typescript: {
// Enables type-aware rules for the given tsconfig
tsconfigPath: 'path/to/tsconfig.json',
},
})
Nuxt config / @scayle/eslint-config-storefront/nuxt
// eslint.config.mjs
import storefrontNuxt from '@scayle/eslint-config-storefront/nuxt'
export default storefrontNuxt()
Storefront Application V3 config / @scayle/eslint-config-storefront/v3
See Storefront Application V3 projects above for the full option list.
All three entry points return a FlatConfigComposer,
so you can chain .append(...), .prepend(...), .override(...), or .renamePlugins(...) to layer
project-specific rules on top.
Plugin names
Rules use each plugin's own published name (@typescript-eslint/*, import-x/*, jsonc/*,
jsdoc/*, node/*, promise/*, sonarjs/*, unicorn/*, regexp/*, e18e/*, vitest/*,
eslint-comments/*). There's no alias renaming. When you override or disable a rule inline,
use that plugin's real prefix:
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
type Foo = { bar: 2 }
e18e/* rules ship as warnings
The e18e community's rule set (modernization, module-replacement, and
performance rules) is enabled by default, but every e18e/* rule ships at warn, not error.
The plugin is still pre-1.0, and its rule set hasn't been reviewed against every consuming
project. A warning won't fail your build.
One rule worth knowing about specifically: e18e/ban-dependencies flags a dependency in your
project that has a lighter recommended alternative (for example ora in favor of
nanospinner). Treat that finding as something to evaluate, not something to fix immediately.
Swapping a dependency is a real decision with its own testing cost, not a lint fix. If you've
decided to keep a flagged dependency, allowlist it explicitly rather than turning the rule off:
export default storefront().append({
rules: {
'e18e/ban-dependencies': ['warn', { allowed: ['ora'] }],
},
})
View what rules are enabled
@eslint/config-inspector is a visual tool maintained by the ESLint core team to help you view what rules are enabled in your project and which files they apply to.
Go into your project root that contains eslint.config.js and run:
npx @eslint/config-inspector
License
Licensed under the MIT License
Related Packages
Eslint sharable config used by the various HTML-validate packages
Version History
| Version | Released | License |
|---|---|---|
| 5.3.1 | MIT | |
| 5.3.0 | MIT | |
| 5.2.2 | MIT | |
| 5.2.1 | MIT | |
| 5.2.0 | MIT | |
| 5.1.1 | MIT | |
| 5.1.0 | MIT | |
| 5.0.0 | MIT | |
| 4.8.2 | MIT | |
| 4.8.1 | MIT | |
| 4.8.0 | MIT | |
| 4.7.25 | MIT | |
| 4.7.24 | MIT | |
| 4.7.23 | MIT | |
| 4.7.22 | MIT | |
| 4.7.21 | MIT | |
| 4.7.20 | MIT | |
| 4.7.19 | MIT | |
| 4.7.18 | MIT | |
| 4.7.17 | MIT | |
| 4.7.16 | MIT | |
| 4.7.15 | MIT | |
| 4.7.14 | MIT | |
| 4.7.13 | MIT | |
| 4.7.12 | MIT | |
| 4.7.11 | MIT | |
| 4.7.10 | MIT | |
| 4.7.9 | MIT | |
| 4.7.8 | MIT | |
| 4.7.7 | MIT |
Showing the latest 30 of 65. See every release on npm
Pre-releases (1)
| Version | Released | License |
|---|---|---|
| 4.8.3-alpha.1 | MIT |