@st1ggy/linter-config 7.3.1 → 7.4.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.
Files changed (47) hide show
  1. package/README.md +88 -8
  2. package/data/oxlint-config-inventory.json +2091 -0
  3. package/data/oxlint-rule-map.json +91859 -0
  4. package/data/oxlint-source-inventory.json +107728 -0
  5. package/docs/OXLINT_RULE_MAPPING.md +2968 -0
  6. package/docs/RULE_MAPPING.md +15 -0
  7. package/package.json +54 -2
  8. package/scripts/README.md +33 -1
  9. package/scripts/init-astro-ox.sh +4 -0
  10. package/scripts/init-common-ox.sh +4 -0
  11. package/scripts/init-next-ox.sh +4 -0
  12. package/scripts/init-react-ox.sh +4 -0
  13. package/scripts/init-solid-ox.sh +4 -0
  14. package/scripts/init-svelte-ox.sh +4 -0
  15. package/scripts/linter-init-core.mjs +80 -20
  16. package/scripts/linter-init.mjs +24 -32
  17. package/src/oxlint/configs/config.astro.js +4092 -0
  18. package/src/oxlint/configs/config.common.js +3209 -0
  19. package/src/oxlint/configs/config.next.js +3592 -0
  20. package/src/oxlint/configs/config.react.js +3503 -0
  21. package/src/oxlint/configs/config.solid.js +4986 -0
  22. package/src/oxlint/configs/config.svelte.js +3301 -0
  23. package/src/oxlint/create-config.js +40 -0
  24. package/src/oxlint/index.d.ts +49 -0
  25. package/src/oxlint/oxlint.config.astro.js +4 -0
  26. package/src/oxlint/oxlint.config.common.js +4 -0
  27. package/src/oxlint/oxlint.config.next.js +4 -0
  28. package/src/oxlint/oxlint.config.react.js +4 -0
  29. package/src/oxlint/oxlint.config.solid.js +4 -0
  30. package/src/oxlint/oxlint.config.svelte.js +4 -0
  31. package/src/oxlint/plugins/astro.js +41 -0
  32. package/src/oxlint/plugins/eslint-core.js +5 -0
  33. package/src/oxlint/plugins/fresh-source-code.js +30 -0
  34. package/src/oxlint/plugins/import-x.js +38 -0
  35. package/src/oxlint/plugins/next.js +1 -0
  36. package/src/oxlint/plugins/prettier.js +1 -0
  37. package/src/oxlint/plugins/react-hooks.js +1 -0
  38. package/src/oxlint/plugins/react.js +1 -0
  39. package/src/oxlint/plugins/solid.js +1 -0
  40. package/src/oxlint/plugins/sonarjs.js +5 -0
  41. package/src/oxlint/plugins/stylistic.js +1 -0
  42. package/src/oxlint/plugins/svelte.js +42 -0
  43. package/src/oxlint/plugins/typescript.js +1 -0
  44. package/src/oxlint/plugins/unicorn.js +5 -0
  45. package/src/oxlint/stacks.js +11 -0
  46. package/scripts/package-smoke.mjs +0 -56
  47. package/scripts/solid-config.test.mjs +0 -134
@@ -33,6 +33,21 @@ Inventory rule counts (sample files in this repository):
33
33
  - `eslint-svelte`: effective rules on `src/examples/example.svelte`
34
34
  - `eslint-astro`: effective rules on `src/examples/example.astro`
35
35
 
36
+ ## Oxlint counterparts
37
+
38
+ The package also exports `common-ox`, `react-ox`, `solid-ox`, `next-ox`, `svelte-ox` and `astro-ox`. Each is one `OxlintConfig` object, used through `oxlint.config.ts`, with `options.typeAware: true` and the native TS7 tsgolint engine. The original ESLint presets remain available.
39
+
40
+ The new variants use explicit migrated rules plus compatible JS adapters, and keep each stack's existing Prettier and Stylelint selections. See [OXLINT_RULE_MAPPING.md](OXLINT_RULE_MAPPING.md) for source/target correspondence, per-scope counts, partial mappings and unsupported rules. Native script extraction does not provide Svelte/Astro template linting.
41
+
42
+ Regenerate and verify the Oxlint inventory with:
43
+
44
+ ```bash
45
+ npm run generate:ox
46
+ npm run inventory:ox
47
+ npm run generate:ox:check
48
+ npm run inventory:ox:check
49
+ ```
50
+
36
51
  ## Stylelint (SCSS preset)
37
52
 
38
53
  | Layer | `@st1ggy/linter-config` | Notes |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@st1ggy/linter-config",
3
- "version": "7.3.1",
3
+ "version": "7.4.0",
4
4
  "description": "ESLint, Prettier, and Stylelint shareable configs (sources under src/).",
5
5
  "private": false,
6
6
  "repository": {
@@ -93,6 +93,36 @@
93
93
  "import": "./src/eslint/eslint.config.common.js",
94
94
  "default": "./src/eslint/eslint.config.common.js"
95
95
  },
96
+ "./common-ox": {
97
+ "types": "./src/oxlint/index.d.ts",
98
+ "import": "./src/oxlint/oxlint.config.common.js",
99
+ "default": "./src/oxlint/oxlint.config.common.js"
100
+ },
101
+ "./react-ox": {
102
+ "types": "./src/oxlint/index.d.ts",
103
+ "import": "./src/oxlint/oxlint.config.react.js",
104
+ "default": "./src/oxlint/oxlint.config.react.js"
105
+ },
106
+ "./solid-ox": {
107
+ "types": "./src/oxlint/index.d.ts",
108
+ "import": "./src/oxlint/oxlint.config.solid.js",
109
+ "default": "./src/oxlint/oxlint.config.solid.js"
110
+ },
111
+ "./next-ox": {
112
+ "types": "./src/oxlint/index.d.ts",
113
+ "import": "./src/oxlint/oxlint.config.next.js",
114
+ "default": "./src/oxlint/oxlint.config.next.js"
115
+ },
116
+ "./svelte-ox": {
117
+ "types": "./src/oxlint/index.d.ts",
118
+ "import": "./src/oxlint/oxlint.config.svelte.js",
119
+ "default": "./src/oxlint/oxlint.config.svelte.js"
120
+ },
121
+ "./astro-ox": {
122
+ "types": "./src/oxlint/index.d.ts",
123
+ "import": "./src/oxlint/oxlint.config.astro.js",
124
+ "default": "./src/oxlint/oxlint.config.astro.js"
125
+ },
96
126
  "./prettier-common": {
97
127
  "types": "./index.d.ts",
98
128
  "import": "./src/prettier/prettier.config.common.js",
@@ -119,7 +149,11 @@
119
149
  "src",
120
150
  "scripts",
121
151
  "data",
122
- "docs"
152
+ "docs",
153
+ "!scripts/fixtures",
154
+ "!scripts/*.test.mjs",
155
+ "!scripts/oxlint-*.mjs",
156
+ "!scripts/package-smoke.mjs"
123
157
  ],
124
158
  "scripts": {
125
159
  "lint": "eslint . && stylelint \"src/**/*.{css,scss}\" --config ./src/stylelint/stylelint.config.scss.js --allow-empty-input && prettier --check .",
@@ -132,6 +166,12 @@
132
166
  "changelog:dry": "conventional-changelog -p conventionalcommits",
133
167
  "publish:npm": "npm publish --access public",
134
168
  "inventory": "node ./scripts/inventory.mjs",
169
+ "typecheck:ox": "node ./node_modules/@typescript/native/bin/tsc --noEmit",
170
+ "generate:ox": "node scripts/oxlint-generate.mjs",
171
+ "generate:ox:check": "node scripts/oxlint-generate.mjs --check",
172
+ "inventory:ox": "node scripts/oxlint-inventory.mjs --write",
173
+ "inventory:ox:check": "node scripts/oxlint-inventory.mjs --source-only --check && node scripts/oxlint-inventory.mjs --check",
174
+ "test:ox": "node --test --test-concurrency=2 scripts/oxlint-*.test.mjs",
135
175
  "config:init": "node ./scripts/linter-init.mjs init --common --dir ./examples/init-smoke",
136
176
  "config:migrate": "node ./scripts/linter-init.mjs migrate --common --dir ./examples/init-smoke",
137
177
  "config:reinit": "node ./scripts/linter-init.mjs reinit --common --dir ./examples/init-smoke",
@@ -148,6 +188,8 @@
148
188
  "eslint-plugin-solid": "^0.18.0",
149
189
  "eslint-plugin-svelte": "^3.22.0",
150
190
  "next": "^16.3.0",
191
+ "oxlint": "1.86.0",
192
+ "oxlint-tsgolint": "7.0.2003",
151
193
  "prettier": "^3.9.6",
152
194
  "prettier-plugin-astro": "^0.14.1",
153
195
  "prettier-plugin-svelte": "^4.1.1",
@@ -180,6 +222,12 @@
180
222
  "next": {
181
223
  "optional": true
182
224
  },
225
+ "oxlint": {
226
+ "optional": true
227
+ },
228
+ "oxlint-tsgolint": {
229
+ "optional": true
230
+ },
183
231
  "astro": {
184
232
  "optional": true
185
233
  },
@@ -203,6 +251,8 @@
203
251
  }
204
252
  },
205
253
  "devDependencies": {
254
+ "@typescript/native": "npm:typescript@7.0.2",
255
+ "pnpm": "10.32.1",
206
256
  "@next/eslint-plugin-next": "^16.3.0",
207
257
  "@stylistic/eslint-plugin-migrate": "^4.4.1",
208
258
  "conventional-changelog": "^8.1.0",
@@ -213,6 +263,8 @@
213
263
  "eslint-plugin-react-hooks": "^7.1.1",
214
264
  "eslint-plugin-solid": "^0.18.0",
215
265
  "eslint-plugin-svelte": "^3.22.0",
266
+ "oxlint": "1.86.0",
267
+ "oxlint-tsgolint": "7.0.2003",
216
268
  "prettier-plugin-astro": "^0.14.1",
217
269
  "prettier-plugin-svelte": "^4.1.1",
218
270
  "solid-js": "^1.9.15",
package/scripts/README.md CHANGED
@@ -41,10 +41,42 @@ npm exec @st1ggy/linter-config -- migrate --svelte
41
41
  npx --yes @st1ggy/linter-config init --astro
42
42
  ```
43
43
 
44
- - **Stack (optional, default common):** `--common` | `--react` | `--solid` | `--next` | `--svelte` | `--astro` (at most one).
44
+ - **ESLint stacks:** `--common` | `--react` | `--solid` | `--next` | `--svelte` | `--astro`.
45
+ - **Oxlint stacks:** `--common-ox` | `--react-ox` | `--solid-ox` | `--next-ox` | `--svelte-ox` | `--astro-ox`.
46
+ - Select at most one stack; the default is `common`.
45
47
  - **`--skip-install`:** only write wrapper files; do not run npm/pnpm/yarn/bun.
46
48
  - Every CLI command opens an interactive wizard. `init` creates missing wrappers; `migrate` can remove selected legacy configs and replaces wrappers.
47
49
 
48
50
  Shell shortcuts in this folder (`init-common.sh`, etc.) call **`node …/linter-init.mjs`** (same script as the published **`@st1ggy/linter-config`** CLI) with the matching stack; pass **`--skip-install`** through when needed.
49
51
 
50
52
  For SolidJS, `init-solid.sh` selects `eslint-solid`, `prettier-common`, and `stylelint-scss`, and installs `eslint-plugin-solid` alongside `@st1ggy/linter-config`.
53
+
54
+ ## Oxlint wrappers
55
+
56
+ The six `init-*-ox.sh` shortcuts select the corresponding `*-ox` stack. They write `oxlint.config.ts`, `prettier.config.js` and `stylelint.config.js`. The wizard pins `oxlint@1.86.0` and `oxlint-tsgolint@7.0.2003` and installs only the framework integrations used by that preset. Svelte projects provide their Svelte runtime; their formatter and ESLint-compatible script rules use it.
57
+
58
+ ```bash
59
+ npx @st1ggy/linter-config init --react-ox
60
+ npx @st1ggy/linter-config migrate --astro-ox
61
+ node ./scripts/linter-init.mjs init --solid-ox --dir ./my-app --skip-install
62
+ ```
63
+
64
+ `init` preserves existing wrappers, including the other engine's config. `migrate` offers the opposite engine config and old `.oxlintrc.*` files for removal before generating the selected wrappers. It never deletes them without confirmation.
65
+
66
+ Oxlint uses native TS7 for typed linting. The consumer tsconfig must be TS7-compatible; the generator does not modify it. Read the [usage guide](../README.md#oxlint-variants) and [coverage report](../docs/OXLINT_RULE_MAPPING.md) before switching a project with Svelte/Astro templates or TS parser-service-dependent JS rules.
67
+
68
+ ## Oxlint development checks
69
+
70
+ ```bash
71
+ node scripts/oxlint-inventory.mjs --source-only
72
+ npm run generate:ox
73
+ npm run inventory:ox
74
+ npm run generate:ox:check
75
+ npm run inventory:ox:check
76
+ npm run test:ox
77
+ npm run typecheck:ox
78
+ node scripts/package-smoke.mjs
79
+ node scripts/package-smoke.mjs --pm pnpm
80
+ ```
81
+
82
+ The parity corpus asserts source and target diagnostics; guardrails reject missing mappings, option/severity drift, absent fixtures and unverified claims of complete SFC coverage. Inventory regeneration is deterministic and does not silently adopt new upstream rules: generated artifacts and fixtures must be reviewed together after dependency updates.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ exec node "${SCRIPT_DIR}/linter-init.mjs" init --astro-ox "$@"
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ exec node "${SCRIPT_DIR}/linter-init.mjs" init --common-ox "$@"
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ exec node "${SCRIPT_DIR}/linter-init.mjs" init --next-ox "$@"
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ exec node "${SCRIPT_DIR}/linter-init.mjs" init --react-ox "$@"
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ exec node "${SCRIPT_DIR}/linter-init.mjs" init --solid-ox "$@"
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ exec node "${SCRIPT_DIR}/linter-init.mjs" init --svelte-ox "$@"
@@ -4,9 +4,13 @@ import { spawnSync } from 'node:child_process'
4
4
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
5
5
  import path from 'node:path'
6
6
 
7
+ import { OX_FRAMEWORK_PACKAGES, OX_VERSIONS } from '../src/oxlint/stacks.js'
8
+
7
9
  export const PACKAGE = '@st1ggy/linter-config'
8
10
 
9
- export const STACK_KEYS = ['common', 'react', 'solid', 'next', 'svelte', 'astro']
11
+ const BASE_STACK_KEYS = ['common', 'react', 'solid', 'next', 'svelte', 'astro']
12
+
13
+ export const STACK_KEYS = [...BASE_STACK_KEYS, ...BASE_STACK_KEYS.map((key) => `${key}-ox`)]
10
14
 
11
15
  export const STACKS = {
12
16
  common: {
@@ -74,6 +78,21 @@ export const STACK_CHOICES = [
74
78
  },
75
79
  ]
76
80
 
81
+ for (const key of BASE_STACK_KEYS) {
82
+ const base = STACKS[key]
83
+
84
+ base.linterFile = 'eslint.config.js'
85
+ base.linterPreset = base.eslint
86
+ STACKS[`${key}-ox`] = {
87
+ linterFile: 'oxlint.config.ts',
88
+ linterPreset: `${key}-ox`,
89
+ prettier: base.prettier,
90
+ stylelint: base.stylelint,
91
+ packages: [...Object.keys(OX_VERSIONS), ...OX_FRAMEWORK_PACKAGES[key]],
92
+ }
93
+ STACK_CHOICES.push({ value: `${key}-ox`, name: `${key}-ox — Oxlint + native TypeScript 7` })
94
+ }
95
+
77
96
  export function readPackageJson(directory) {
78
97
  const filePath = path.join(directory, 'package.json')
79
98
 
@@ -103,7 +122,7 @@ export function isSelfPackageRoot(directory) {
103
122
  return packageJson?.name === PACKAGE
104
123
  }
105
124
 
106
- export function hasResolvablePackage(startDirectory, packageName = PACKAGE) {
125
+ function packageManifestPath(startDirectory, packageName) {
107
126
  let currentDirectory = path.resolve(startDirectory)
108
127
  const packageParts = packageName.split('/')
109
128
 
@@ -111,7 +130,7 @@ export function hasResolvablePackage(startDirectory, packageName = PACKAGE) {
111
130
  const marker = path.join(currentDirectory, 'node_modules', ...packageParts, 'package.json')
112
131
 
113
132
  if (existsSync(marker)) {
114
- return true
133
+ return marker
115
134
  }
116
135
 
117
136
  const parentDirectory = path.dirname(currentDirectory)
@@ -123,7 +142,25 @@ export function hasResolvablePackage(startDirectory, packageName = PACKAGE) {
123
142
  currentDirectory = parentDirectory
124
143
  }
125
144
 
126
- return false
145
+ return null
146
+ }
147
+
148
+ export function hasResolvablePackage(startDirectory, packageName = PACKAGE) {
149
+ return packageManifestPath(startDirectory, packageName) !== null
150
+ }
151
+
152
+ function hasRequestedVersion(directory, packageName, spec) {
153
+ const marker = packageManifestPath(directory, packageName)
154
+
155
+ if (!marker) {
156
+ return false
157
+ }
158
+
159
+ try {
160
+ return JSON.parse(readFileSync(marker, 'utf8')).version === spec.split('@').at(-1)
161
+ } catch {
162
+ return false
163
+ }
127
164
  }
128
165
 
129
166
  export function detectPackageManager(startDirectory) {
@@ -170,7 +207,7 @@ export function runPmSync(command, args, cwd) {
170
207
 
171
208
  // skipInstall: skip npm install; options.quiet: suppress stderr warnings
172
209
  export function ensureDevDependencies(targetDirectory, packages, shouldSkipInstall, options = {}) {
173
- const { quiet = false } = options
210
+ const { quiet = false, specs = {} } = options
174
211
 
175
212
  if (shouldSkipInstall) {
176
213
  return
@@ -197,7 +234,11 @@ export function ensureDevDependencies(targetDirectory, packages, shouldSkipInsta
197
234
  }
198
235
 
199
236
  const dependencies = mergedDependencies(packageJson)
200
- const packagesToAdd = packages.filter((packageName) => !Object.hasOwn(dependencies, packageName))
237
+ const packagesToAdd = packages.filter(
238
+ (packageName) =>
239
+ !Object.hasOwn(dependencies, packageName) ||
240
+ (specs[packageName] && !hasRequestedVersion(directory, packageName, specs[packageName])),
241
+ )
201
242
  const hasUnresolvedPackage = packages.some((packageName) => !hasResolvablePackage(directory, packageName))
202
243
 
203
244
  if (!hasUnresolvedPackage && packagesToAdd.length === 0) {
@@ -223,15 +264,18 @@ export function ensureDevDependencies(targetDirectory, packages, shouldSkipInsta
223
264
  return
224
265
  }
225
266
 
267
+ const requested = packagesToAdd.map((name) => specs[name] ?? name)
268
+ const shouldPin = requested.some((name, index) => name !== packagesToAdd[index])
269
+
226
270
  if (!quiet) {
227
- process.stdout.write(`install: ${pm} add -D ${packagesToAdd.join(' ')} (${directory})\n`)
271
+ process.stdout.write(`install: ${pm} add -D ${requested.join(' ')} (${directory})\n`)
228
272
  }
229
273
 
230
274
  const add = {
231
- npm: () => runPmSync('npm', ['install', '-D', ...packagesToAdd], directory),
232
- pnpm: () => runPmSync('pnpm', ['add', '-D', ...packagesToAdd], directory),
233
- yarn: () => runPmSync('yarn', ['add', '-D', ...packagesToAdd], directory),
234
- bun: () => runPmSync('bun', ['add', '-d', ...packagesToAdd], directory),
275
+ npm: () => runPmSync('npm', ['install', '-D', ...(shouldPin ? ['--save-exact'] : []), ...requested], directory),
276
+ pnpm: () => runPmSync('pnpm', ['add', '-D', ...(shouldPin ? ['--save-exact'] : []), ...requested], directory),
277
+ yarn: () => runPmSync('yarn', ['add', '-D', ...(shouldPin ? ['--exact'] : []), ...requested], directory),
278
+ bun: () => runPmSync('bun', ['add', '-d', ...(shouldPin ? ['--exact'] : []), ...requested], directory),
235
279
  }
236
280
 
237
281
  add[pm]()
@@ -251,6 +295,14 @@ export function stackPackages(stackKey) {
251
295
  return [PACKAGE, ...stack.packages]
252
296
  }
253
297
 
298
+ export function stackPackageSpecs(stackKey) {
299
+ stackPackages(stackKey)
300
+
301
+ return stackKey.endsWith('-ox')
302
+ ? Object.fromEntries(Object.entries(OX_VERSIONS).map(([name, version]) => [name, `${name}@${version}`]))
303
+ : {}
304
+ }
305
+
254
306
  export function printHelp() {
255
307
  process.stdout.write(`\
256
308
  ${PACKAGE} — generate local wrapper configs in a consumer project.
@@ -264,13 +316,15 @@ Commands:
264
316
  create Alias for init.
265
317
 
266
318
  Writes (each command):
267
- eslint.config.js, prettier.config.js, stylelint.config.js — re-exports for one stack.
319
+ ESLint stacks: eslint.config.js, prettier.config.js, stylelint.config.js.
320
+ Oxlint stacks: oxlint.config.ts, prettier.config.js, stylelint.config.js.
268
321
 
269
322
  Unless --skip-install: if package.json exists, runs the detected package manager to add or sync
270
323
  ${PACKAGE} and the selected stack's integration plugins.
271
324
 
272
325
  Stack (at most one; default: common):
273
326
  --common | --react | --solid | --next | --svelte | --astro
327
+ --common-ox | --react-ox | --solid-ox | --next-ox | --svelte-ox | --astro-ox
274
328
 
275
329
  Options:
276
330
  --dir, -d Target directory (default: current working directory).
@@ -282,6 +336,7 @@ Examples (after: npm i -D ${PACKAGE}):
282
336
  npx ${PACKAGE} init
283
337
  npx ${PACKAGE} init --react
284
338
  npx ${PACKAGE} init --solid
339
+ npx ${PACKAGE} init --solid-ox
285
340
  npx ${PACKAGE} migrate --svelte --dir ./apps/web
286
341
  npx ${PACKAGE} init --astro
287
342
  npm exec ${PACKAGE} -- init --common
@@ -324,23 +379,28 @@ export function writeFile(targetDirectory, name, content, overwrite, options = {
324
379
  return true
325
380
  }
326
381
 
327
- export function wrapperFileNames() {
328
- return ['eslint.config.js', 'prettier.config.js', 'stylelint.config.js']
382
+ export function wrapperFileNames(stackKey = 'common') {
383
+ stackPackages(stackKey)
384
+
385
+ return [STACKS[stackKey].linterFile, 'prettier.config.js', 'stylelint.config.js']
329
386
  }
330
387
 
331
- export function existingWrapperFiles(targetDirectory) {
332
- return wrapperFileNames().filter((name) => existsSync(path.join(targetDirectory, name)))
388
+ export function existingWrapperFiles(targetDirectory, stackKey = 'common') {
389
+ return wrapperFileNames(stackKey).filter((name) => existsSync(path.join(targetDirectory, name)))
333
390
  }
334
391
 
335
- export function legacyConfigFiles(targetDirectory) {
392
+ export function legacyConfigFiles(targetDirectory, stackKey = 'common') {
336
393
  if (!existsSync(targetDirectory)) {
337
394
  return []
338
395
  }
339
396
 
340
- const wrappers = new Set(wrapperFileNames())
397
+ const wrappers = new Set(wrapperFileNames(stackKey))
341
398
 
342
399
  return readdirSync(targetDirectory, { withFileTypes: true })
343
- .filter((entry) => entry.isFile() && /(?:eslint|prettier|stylelint)/i.test(entry.name) && !wrappers.has(entry.name))
400
+ .filter(
401
+ (entry) =>
402
+ entry.isFile() && /(?:eslint|oxlint|prettier|stylelint)/i.test(entry.name) && !wrappers.has(entry.name),
403
+ )
344
404
  .map((entry) => entry.name)
345
405
  .toSorted((left, right) => left.localeCompare(right))
346
406
  }
@@ -383,7 +443,7 @@ export function run(mode, targetDirectory, stackKey, options = {}) {
383
443
 
384
444
  const shouldOverwrite = mode === 'migrate' || mode === 'reinit'
385
445
 
386
- writeFile(targetDirectory, 'eslint.config.js', jsReExport(stack.eslint), shouldOverwrite, { quiet })
446
+ writeFile(targetDirectory, stack.linterFile, jsReExport(stack.linterPreset), shouldOverwrite, { quiet })
387
447
  writeFile(targetDirectory, 'prettier.config.js', jsReExport(stack.prettier), shouldOverwrite, { quiet })
388
448
  writeFile(targetDirectory, 'stylelint.config.js', jsReExport(stack.stylelint), shouldOverwrite, { quiet })
389
449
  }
@@ -20,27 +20,15 @@ import {
20
20
  resolveStackKey,
21
21
  resolveTargetDirectory,
22
22
  run,
23
+ stackPackageSpecs,
23
24
  stackPackages,
25
+ wrapperFileNames,
24
26
  } from './linter-init-core.mjs'
25
27
 
26
28
  function parseArgv(argv) {
27
29
  return minimist(argv, {
28
30
  string: ['dir', 'd'],
29
- boolean: [
30
- 'help',
31
- 'h',
32
- 'skip-install',
33
- 'eslint',
34
- 'biome',
35
- 'common',
36
- 'react',
37
- 'solid',
38
- 'next',
39
- 'svelte',
40
- 'astro',
41
- 'interactive',
42
- 'i',
43
- ],
31
+ boolean: ['help', 'h', 'skip-install', 'eslint', 'biome', ...STACK_KEYS, 'interactive', 'i'],
44
32
  alias: {
45
33
  h: 'help',
46
34
  d: 'dir',
@@ -58,12 +46,7 @@ function argvToValues(argv) {
58
46
  'skip-install': Boolean(argv['skip-install']),
59
47
  eslint: Boolean(argv.eslint),
60
48
  biome: Boolean(argv.biome),
61
- common: Boolean(argv.common),
62
- react: Boolean(argv.react),
63
- solid: Boolean(argv.solid),
64
- next: Boolean(argv.next),
65
- svelte: Boolean(argv.svelte),
66
- astro: Boolean(argv.astro),
49
+ ...Object.fromEntries(STACK_KEYS.map((key) => [key, argv[key] === true])),
67
50
  }
68
51
  }
69
52
 
@@ -77,7 +60,7 @@ async function selectCommand(command) {
77
60
  choices: [
78
61
  {
79
62
  value: 'init',
80
- name: 'init — create missing ESLint, Prettier and Stylelint wrappers',
63
+ name: 'init — create missing linter, Prettier and Stylelint wrappers',
81
64
  },
82
65
  {
83
66
  value: 'migrate',
@@ -102,14 +85,14 @@ async function selectStack(values) {
102
85
  })
103
86
  }
104
87
 
105
- async function selectLegacyFiles(command, directory) {
88
+ async function selectLegacyFiles(command, directory, stack) {
106
89
  if (command !== 'migrate') {
107
90
  return []
108
91
  }
109
92
 
110
93
  const selected = []
111
94
 
112
- for (const file of legacyConfigFiles(directory)) {
95
+ for (const file of legacyConfigFiles(directory, stack)) {
113
96
  if (await confirm({ message: `Remove legacy config ${file}?`, default: false })) {
114
97
  selected.push(file)
115
98
  }
@@ -122,7 +105,7 @@ function printSummary(command, stack, directory, shouldSkipInstall, existing, fi
122
105
  const wrapperFiles =
123
106
  command === 'init' && existing.length > 0
124
107
  ? `create missing; keep ${existing.join(', ')}`
125
- : 'write eslint.config.js, prettier.config.js, stylelint.config.js'
108
+ : `write ${wrapperFileNames(stack).join(', ')}`
126
109
 
127
110
  process.stdout.write('\n')
128
111
  process.stdout.write(` Action: ${command}\n`)
@@ -148,11 +131,20 @@ async function runInteractive(command, values) {
148
131
  }
149
132
 
150
133
  const shouldSkipInstall = values['skip-install'] === true
151
- const existing = existingWrapperFiles(resolved)
152
- const filesToRemove = await selectLegacyFiles(selectedCommand, resolved)
134
+ const existing = existingWrapperFiles(resolved, selectedStack)
135
+ const filesToRemove = await selectLegacyFiles(selectedCommand, resolved, selectedStack)
153
136
  const packages = stackPackages(selectedStack)
154
-
155
- printSummary(selectedCommand, selectedStack, resolved, shouldSkipInstall, existing, filesToRemove, packages)
137
+ const specs = stackPackageSpecs(selectedStack)
138
+
139
+ printSummary(
140
+ selectedCommand,
141
+ selectedStack,
142
+ resolved,
143
+ shouldSkipInstall,
144
+ existing,
145
+ filesToRemove,
146
+ packages.map((name) => specs[name] ?? name),
147
+ )
156
148
 
157
149
  const shouldProceed = await confirm({
158
150
  message: 'Proceed?',
@@ -175,7 +167,7 @@ async function runInteractive(command, values) {
175
167
  }).start()
176
168
 
177
169
  try {
178
- ensureDevDependencies(resolved, packages, false, { quiet: true })
170
+ ensureDevDependencies(resolved, packages, false, { quiet: true, specs })
179
171
  spinner.succeed(`${selectedStack} linting packages are available`)
180
172
  } catch (error) {
181
173
  spinner.fail('Dependency step failed')
@@ -185,7 +177,7 @@ async function runInteractive(command, values) {
185
177
 
186
178
  const writeSpinner = ora({
187
179
  color: 'green',
188
- text: 'Writing eslint.config.js, prettier.config.js, stylelint.config.js…',
180
+ text: `Writing ${wrapperFileNames(selectedStack).join(', ')}…`,
189
181
  }).start()
190
182
 
191
183
  try {
@@ -213,7 +205,7 @@ function normalizeCommand(command) {
213
205
  function warnLegacyFlags(values) {
214
206
  if (values.biome === true) {
215
207
  process.stderr.write(
216
- `warning: --biome is ignored; Biome presets were removed from ${PACKAGE}. Only ESLint, Prettier, and Stylelint wrappers are written.\n`,
208
+ `warning: --biome is ignored; Biome presets were removed from ${PACKAGE}. Select an ESLint or Oxlint stack.\n`,
217
209
  )
218
210
  }
219
211
  }