@syntopica/eslint-config 0.8.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) BusiRocket
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PUBLIC_API.md ADDED
@@ -0,0 +1,47 @@
1
+ # Public API (`@syntopica/eslint-config`)
2
+
3
+ Semver applies to **export subpaths** listed below. Import paths not listed here
4
+ are **private** and may change without a major bump.
5
+
6
+ ## Stable exports (semver)
7
+
8
+ | Export subpath | Purpose |
9
+ | ---------------------------------------------- | ----------------------------------------------------------------- |
10
+ | `@syntopica/eslint-config/base` | Core JS/TS correctness, imports, promise, security, Prettier last |
11
+ | `@syntopica/eslint-config/nextjs` | Next.js App Router + React + frontend boundaries |
12
+ | `@syntopica/eslint-config/vite-react` | Vite + React + frontend boundaries |
13
+ | `@syntopica/eslint-config/astro` | Astro + TS + frontend boundaries |
14
+ | `@syntopica/eslint-config/node` | Node globals + unicorn prefer-node-protocol |
15
+ | `@syntopica/eslint-config/nestjs` | Node preset + NestJS decorator-aware rule tweaks |
16
+ | `@syntopica/eslint-config/code-quality` | Sonar + code-policy structural rules |
17
+ | `@syntopica/eslint-config/accessibility` | jsx-a11y |
18
+ | `@syntopica/eslint-config/tailwind` | Tailwind CSS plugin |
19
+ | `@syntopica/eslint-config/frontend-boundaries` | `eslint-plugin-boundaries` layer map only |
20
+ | `@syntopica/eslint-config/testing` | Vitest + Testing Library rules for test files |
21
+
22
+ Each entry resolves to **TypeScript source** (`*.ts`) published in the package.
23
+ Consumers load flat config with **ESM** and a TypeScript-aware runner (for
24
+ example `jiti` or your bundler) as shown in the package README.
25
+
26
+ ## Implementation detail
27
+
28
+ Internal modules under `src/` that are not re-exported through the table above
29
+ are **not** public API.
30
+
31
+ ## `publint`/`attw` findings that are expected, not defects
32
+
33
+ `publish:check` (`publint --strict && attw --pack . --profile node16`) ignores
34
+ two `attw` rules for this package: `cjs-resolves-to-esm` and
35
+ `internal-resolution-error`. Both are consequences of the no-build design above,
36
+ not resolution bugs:
37
+
38
+ - **`cjs-resolves-to-esm`** — every export subpath resolves to raw `.ts` ESM
39
+ source; there is no CommonJS build to satisfy a `require()` caller. Consumers
40
+ load these configs with ESM and a TypeScript-aware runtime (`jiti`), never
41
+ `require`.
42
+ - **`internal-resolution-error`** — subpaths such as `./nextjs` and `./astro`
43
+ import framework packages (`@next/eslint-plugin-next`, `eslint-plugin-astro`,
44
+ etc.) declared as **optional peer dependencies**. `attw`'s sandbox only
45
+ contains this package's own tarball, so it cannot see peers a real consumer
46
+ installs, and flags those imports as unresolved. This is the intended shape of
47
+ an optional peer, not a broken `exports` entry.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # @syntopica/eslint-config
2
+
3
+ Flat ESLint configuration for JavaScript and TypeScript projects: shared
4
+ **base** rules plus optional layers for Next.js, Vite + React, Astro, Node,
5
+ Tailwind, accessibility, and code-quality (including
6
+ `eslint-plugin-code-policy`).
7
+
8
+ - **Public API (semver):** see [PUBLIC_API.md](./PUBLIC_API.md).
9
+ - **Platform decisions:**
10
+ [engineering-baseline/docs/platform-decisions.md](https://github.com/BusiRocket/engineering-baseline/blob/main/docs/platform-decisions.md).
11
+
12
+ ## Requirements
13
+
14
+ - Node.js 20+
15
+ - ESLint 9+ (flat config)
16
+ - TypeScript 5.4+ for type-aware linting
17
+
18
+ Install `eslint` and `typescript` in your project, plus every package listed for
19
+ the subpaths you import - see [Stacks](#stacks).
20
+
21
+ This package ships **TypeScript source**, so each subpath's `import` statements
22
+ are resolved from your project, not from a build. Under pnpm's isolated
23
+ `node_modules` that means a plugin must be a direct dependency of the consumer
24
+ even when it is a `dependencies` entry here: a missing one fails `tsc --noEmit`
25
+ with `Cannot find module '<plugin>'` before ESLint ever runs. Optional peers are
26
+ listed in `package.json` under `peerDependencies` / `peerDependenciesMeta`.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pnpm add -D @syntopica/eslint-config@^0.1.0 eslint typescript
32
+ ```
33
+
34
+ Add peers for the stacks you use (React, Next.js, Astro, and so on). The
35
+ **base** stack ships several dependencies bundled with this package;
36
+ framework-specific plugins remain peers.
37
+
38
+ ## New project
39
+
40
+ 1. Add `@syntopica/eslint-config` and peers for your stack.
41
+ 2. Add `eslint.config.ts` (or `.mjs`) using `jiti` / `tsx` / Node 22+ TypeScript
42
+ support as needed.
43
+ 3. Follow
44
+ [docs/adoption/new-repo.md](https://github.com/BusiRocket/engineering-baseline/blob/main/docs/adoption/new-repo.md).
45
+
46
+ Minimal `eslint.config.ts` (Node / library):
47
+
48
+ ```ts
49
+ import { createBaseConfig } from '@syntopica/eslint-config/base'
50
+
51
+ export default createBaseConfig({ tsconfigRootDir: import.meta.dirname })
52
+ ```
53
+
54
+ Next.js App Router: import `createNextjsConfig` from
55
+ `@syntopica/eslint-config/nextjs` and compose with `createBaseConfig` as in the
56
+ the Next.js template in
57
+ [engineering-baseline](https://github.com/BusiRocket/engineering-baseline/tree/main/templates/nextjs-app).
58
+
59
+ ## Existing project
60
+
61
+ See
62
+ [docs/adoption/existing-repo.md](https://github.com/BusiRocket/engineering-baseline/blob/main/docs/adoption/existing-repo.md)
63
+ and
64
+ [docs/migration.md](https://github.com/BusiRocket/engineering-baseline/blob/main/docs/migration.md).
65
+ Migrate to flat config first, then layer `@syntopica/eslint-config`.
66
+
67
+ ## Stacks
68
+
69
+ Install the packages in the right-hand column alongside the subpath you import.
70
+ The list is transitive: `/code-quality` also pulls in what `/testing` needs, and
71
+ `/nestjs` what `/node` needs.
72
+
73
+ | Import subpath | Use case | Install alongside |
74
+ | ---------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
75
+ | `/base` | Core TS/JS, imports, promise, security | `@eslint/js`, `eslint-config-prettier`, `eslint-plugin-import`, `eslint-import-resolver-typescript`, `eslint-plugin-promise`, `eslint-plugin-security`, `eslint-plugin-unused-imports`, `typescript-eslint` |
76
+ | `/nextjs` | Next.js + React + boundaries | `@next/eslint-plugin-next`, `eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-boundaries` |
77
+ | `/vite-react` | Vite + React + boundaries | `eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-react-refresh`, `eslint-plugin-boundaries` |
78
+ | `/vite-vue` | Vite + Vue | `eslint-plugin-vue`, `eslint-plugin-vuejs-accessibility`, `vue-eslint-parser`, `eslint-plugin-unused-imports`, `typescript-eslint` |
79
+ | `/astro` | Astro | `eslint-plugin-astro`, `typescript-eslint`, `eslint-plugin-boundaries` |
80
+ | `/node` | Node libraries | `eslint-plugin-unicorn`, `globals` |
81
+ | `/nestjs` | NestJS services | `eslint-plugin-unicorn`, `globals` |
82
+ | `/code-quality` | Sonar + code-policy | `eslint-plugin-code-policy`, `eslint-plugin-sonarjs`, `eslint-plugin-testing-library`, `@vitest/eslint-plugin` |
83
+ | `/testing` | Vitest + Testing Library | `@vitest/eslint-plugin`, `eslint-plugin-testing-library` |
84
+ | `/accessibility` | jsx-a11y | `eslint-plugin-jsx-a11y` |
85
+ | `/tailwind` | Tailwind plugin | `eslint-plugin-tailwindcss` |
86
+ | `/frontend-boundaries` | Boundaries only | `eslint-plugin-boundaries` |
87
+
88
+ `/code-quality` composes `/testing` unconditionally, so
89
+ `eslint-plugin-testing-library` is required there even in a project with no
90
+ tests.
91
+
92
+ ## CLI helper
93
+
94
+ ```bash
95
+ pnpm dlx @syntopica/create-baseline@^0.1.0 --check
96
+ ```
97
+
98
+ `--soft` prints install commands; `--hard` also requires `eslint.config.*` in
99
+ the repo root.
100
+
101
+ ## Related
102
+
103
+ - **Plugin (rules only):** `eslint-plugin-code-policy` — full baseline is
104
+ composed here, not in the plugin.
105
+ - **Prettier / TS configs:** `@syntopica/prettier-config`,
106
+ `@syntopica/tsconfig`.
@@ -0,0 +1,7 @@
1
+ import { createBaseConfig } from './src/base.ts'
2
+ import { createNodeConfig } from './src/node.ts'
3
+
4
+ export default [
5
+ ...createBaseConfig({ tsconfigRootDir: import.meta.dirname }),
6
+ ...createNodeConfig(),
7
+ ]
package/package.json ADDED
@@ -0,0 +1,186 @@
1
+ {
2
+ "name": "@syntopica/eslint-config",
3
+ "version": "0.8.0",
4
+ "private": false,
5
+ "packageManager": "pnpm@11.25.0",
6
+ "license": "MIT",
7
+ "description": "Shared ESLint configuration baseline",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/BusiRocket/baseline.git",
11
+ "directory": "packages/eslint-config"
12
+ },
13
+ "homepage": "https://github.com/BusiRocket/baseline/tree/main/packages/eslint-config#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/BusiRocket/baseline/issues"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "type": "module",
21
+ "engines": {
22
+ "node": ">=20"
23
+ },
24
+ "files": [
25
+ "LICENSE",
26
+ "README.md",
27
+ "src",
28
+ "eslint.config.ts",
29
+ "tsconfig.json",
30
+ "PUBLIC_API.md"
31
+ ],
32
+ "exports": {
33
+ "./base": "./src/base.ts",
34
+ "./nextjs": "./src/nextjs.ts",
35
+ "./astro": "./src/astro.ts",
36
+ "./vite-react": "./src/vite-react.ts",
37
+ "./vite-vue": "./src/vite-vue.ts",
38
+ "./node": "./src/node.ts",
39
+ "./nestjs": "./src/nestjs.ts",
40
+ "./code-quality": "./src/code-quality.ts",
41
+ "./accessibility": "./src/accessibility.ts",
42
+ "./tailwind": "./src/tailwind.ts",
43
+ "./frontend-boundaries": "./src/frontend-boundaries.ts",
44
+ "./testing": "./src/testing.ts",
45
+ "./data-files": "./src/data-files.ts"
46
+ },
47
+ "scripts": {
48
+ "fix": "pnpm run lint:fix",
49
+ "lint": "eslint src --max-warnings 0",
50
+ "lint:fix": "eslint src --fix",
51
+ "lint:suppress": "eslint src --suppress-all",
52
+ "lint:prune": "eslint src --prune-suppressions",
53
+ "type-check": "tsc --noEmit -p tsconfig.json",
54
+ "publish:check": "baseline-licenses && publint --strict && attw --pack . --profile node16 --ignore-rules cjs-resolves-to-esm internal-resolution-error"
55
+ },
56
+ "dependencies": {
57
+ "@eslint/js": "^10.0.1",
58
+ "eslint-config-prettier": "^10.1.8",
59
+ "eslint-import-resolver-typescript": "^4.4.5",
60
+ "eslint-plugin-import": "^2.32.0",
61
+ "eslint-plugin-promise": "^7.3.0",
62
+ "eslint-plugin-security": "^4.0.1",
63
+ "eslint-plugin-unused-imports": "^4.4.1",
64
+ "typescript-eslint": "^8.68.0"
65
+ },
66
+ "peerDependencies": {
67
+ "@eslint/markdown": ">=8.0.0",
68
+ "@next/eslint-plugin-next": ">=15.0.0",
69
+ "@vitest/eslint-plugin": ">=1.0.0",
70
+ "eslint": ">=9.0.0",
71
+ "eslint-config-next": ">=15.0.0",
72
+ "eslint-plugin-astro": ">=0.34.0",
73
+ "eslint-plugin-boundaries": ">=7.0.0",
74
+ "eslint-plugin-code-policy": ">=0.4.0",
75
+ "eslint-plugin-jsonc": ">=3.0.0",
76
+ "eslint-plugin-jsx-a11y": ">=6.8.0",
77
+ "eslint-plugin-react": ">=7.37.0",
78
+ "eslint-plugin-react-hooks": ">=5.0.0",
79
+ "eslint-plugin-react-refresh": ">=0.4.0",
80
+ "eslint-plugin-regexp": ">=2.7.0",
81
+ "eslint-plugin-sonarjs": ">=3.0.0",
82
+ "eslint-plugin-tailwindcss": "^4.2.0",
83
+ "eslint-plugin-testing-library": ">=7.0.0",
84
+ "eslint-plugin-unicorn": ">=57.0.0",
85
+ "eslint-plugin-vue": ">=10.0.0",
86
+ "eslint-plugin-vuejs-accessibility": ">=2.4.0",
87
+ "eslint-plugin-yml": ">=3.0.0",
88
+ "globals": ">=15.0.0",
89
+ "typescript": ">=5.4.0",
90
+ "vue-eslint-parser": ">=10.0.0"
91
+ },
92
+ "peerDependenciesMeta": {
93
+ "@next/eslint-plugin-next": {
94
+ "optional": true
95
+ },
96
+ "@vitest/eslint-plugin": {
97
+ "optional": true
98
+ },
99
+ "eslint-config-next": {
100
+ "optional": true
101
+ },
102
+ "eslint-plugin-astro": {
103
+ "optional": true
104
+ },
105
+ "eslint-plugin-boundaries": {
106
+ "optional": true
107
+ },
108
+ "eslint-plugin-code-policy": {
109
+ "optional": true
110
+ },
111
+ "eslint-plugin-jsx-a11y": {
112
+ "optional": true
113
+ },
114
+ "eslint-plugin-react": {
115
+ "optional": true
116
+ },
117
+ "eslint-plugin-react-hooks": {
118
+ "optional": true
119
+ },
120
+ "eslint-plugin-react-refresh": {
121
+ "optional": true
122
+ },
123
+ "eslint-plugin-sonarjs": {
124
+ "optional": true
125
+ },
126
+ "eslint-plugin-tailwindcss": {
127
+ "optional": true
128
+ },
129
+ "eslint-plugin-testing-library": {
130
+ "optional": true
131
+ },
132
+ "eslint-plugin-unicorn": {
133
+ "optional": true
134
+ },
135
+ "eslint-plugin-vue": {
136
+ "optional": true
137
+ },
138
+ "eslint-plugin-vuejs-accessibility": {
139
+ "optional": true
140
+ },
141
+ "globals": {
142
+ "optional": true
143
+ },
144
+ "vue-eslint-parser": {
145
+ "optional": true
146
+ },
147
+ "@eslint/markdown": {
148
+ "optional": true
149
+ },
150
+ "eslint-plugin-jsonc": {
151
+ "optional": true
152
+ },
153
+ "eslint-plugin-yml": {
154
+ "optional": true
155
+ }
156
+ },
157
+ "devDependencies": {
158
+ "@arethetypeswrong/cli": "^0.18.5",
159
+ "@eslint/markdown": "^8.0.3",
160
+ "@next/eslint-plugin-next": "^16.3.3",
161
+ "@types/node": "^26.4.0",
162
+ "@vitest/eslint-plugin": "^1.6.27",
163
+ "eslint": "^10.9.1",
164
+ "eslint-plugin-astro": "^3.1.0",
165
+ "eslint-plugin-boundaries": "^7.2.0",
166
+ "eslint-plugin-code-policy": "workspace:*",
167
+ "eslint-plugin-jsonc": "^3.4.2",
168
+ "eslint-plugin-jsx-a11y": "^6.10.2",
169
+ "eslint-plugin-react": "^7.37.5",
170
+ "eslint-plugin-react-hooks": "^7.1.1",
171
+ "eslint-plugin-react-refresh": "^0.5.5",
172
+ "eslint-plugin-regexp": "^3.2.0",
173
+ "eslint-plugin-sonarjs": "^4.2.0",
174
+ "eslint-plugin-tailwindcss": "^4.4.0",
175
+ "eslint-plugin-testing-library": "^7.16.2",
176
+ "eslint-plugin-unicorn": "^74.0.0",
177
+ "eslint-plugin-vue": "^10.10.0",
178
+ "eslint-plugin-vuejs-accessibility": "^2.6.0",
179
+ "eslint-plugin-yml": "^3.8.1",
180
+ "globals": "^17.11.0",
181
+ "jiti": "^2.7.0",
182
+ "publint": "^0.3.24",
183
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
184
+ "vue-eslint-parser": "^10.4.1"
185
+ }
186
+ }
@@ -0,0 +1,26 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
2
+ const jsxA11y = require('eslint-plugin-jsx-a11y') as {
3
+ flatConfigs: { recommended: unknown; strict: unknown }
4
+ }
5
+
6
+ /**
7
+ * Accessibility rules for JSX-based projects (React, Next.js, Astro).
8
+ * Enforces WCAG compliance at lint time: alt text, ARIA roles,
9
+ * keyboard navigation, form labels, and more.
10
+ *
11
+ * `strict`, not `recommended`. The two differ on the rules that catch the
12
+ * failures a sighted mouse user never sees: a `role` on an element that
13
+ * already has one, an interactive handler on a `div` with no keyboard
14
+ * equivalent, a label that is adjacent to its control rather than associated
15
+ * with it. `recommended` relaxes exactly those to keep large existing
16
+ * codebases green, which is the wrong trade for a baseline whose templates
17
+ * start at zero findings.
18
+ *
19
+ * Static analysis is half the gate: it reads the source, not the rendered
20
+ * tree, so a violation composed at runtime is invisible to it. The templates
21
+ * pair this with an axe assertion over the rendered component.
22
+ *
23
+ * Spread this as the final layer (after framework config, before architecture):
24
+ * ...createAccessibilityConfig()
25
+ */
26
+ export const createAccessibilityConfig = () => [jsxA11y.flatConfigs.strict]
package/src/astro.ts ADDED
@@ -0,0 +1,62 @@
1
+ import astroPlugin from 'eslint-plugin-astro'
2
+ import { globalIgnores } from 'eslint/config'
3
+ import tseslint from 'typescript-eslint'
4
+
5
+ import { createFrontendBoundariesConfig } from './frontend-boundaries'
6
+
7
+ export type AstroConfigOptions = {
8
+ tsconfigRootDir?: string
9
+ }
10
+
11
+ type FlatConfigLike = Record<string, unknown>
12
+ const astroConfigs = astroPlugin.configs as unknown as Record<
13
+ string,
14
+ FlatConfigLike[]
15
+ >
16
+ const astroRecommended = astroConfigs['flat/recommended'] ?? []
17
+ const astroA11yRecommended = astroConfigs['flat/jsx-a11y-recommended'] ?? []
18
+
19
+ export const createAstroConfig = (options: AstroConfigOptions = {}) => {
20
+ const tsconfigRootDir = options.tsconfigRootDir ?? process.cwd()
21
+
22
+ return [
23
+ globalIgnores(['dist/**', '.astro/**', 'coverage/**']),
24
+ ...astroRecommended,
25
+ ...astroA11yRecommended,
26
+ {
27
+ files: ['**/*.astro'],
28
+ languageOptions: {
29
+ parserOptions: {
30
+ parser: tseslint.parser,
31
+ project: true,
32
+ tsconfigRootDir,
33
+ extraFileExtensions: ['.astro'],
34
+ },
35
+ },
36
+ },
37
+ {
38
+ files: ['**/*.{ts,tsx}'],
39
+ languageOptions: {
40
+ parserOptions: {
41
+ projectService: true,
42
+ tsconfigRootDir,
43
+ },
44
+ },
45
+ rules: {
46
+ '@typescript-eslint/no-confusing-void-expression': 'off',
47
+ '@typescript-eslint/unbound-method': 'off',
48
+ '@typescript-eslint/require-await': 'off',
49
+ '@typescript-eslint/no-misused-promises': [
50
+ 'error',
51
+ { checksVoidReturn: false },
52
+ ],
53
+ },
54
+ },
55
+ {
56
+ files: ['**/*.d.ts'],
57
+ rules: { '@typescript-eslint/triple-slash-reference': 'off' },
58
+ },
59
+ // Same layered boundaries as Next.js / Vite React (components, services, hooks, …)
60
+ ...createFrontendBoundariesConfig(),
61
+ ]
62
+ }