@st1ggy/linter-config 5.0.0 → 6.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -44
- package/{packages/eslint/data → data}/linter-config-inventory.json +4 -5
- package/docs/RULE_MAPPING.md +38 -0
- package/index.d.ts +0 -32
- package/package.json +43 -80
- package/scripts/README.md +23 -18
- package/scripts/init-common.sh +6 -0
- package/scripts/init-next.sh +5 -0
- package/scripts/init-react.sh +5 -0
- package/scripts/init-svelte.sh +5 -0
- package/{packages/eslint/scripts → scripts}/inventory.mjs +5 -5
- package/scripts/linter-init-core.mjs +317 -0
- package/scripts/linter-init.mjs +243 -0
- package/scripts/remove-current.sh +17 -0
- package/{packages/eslint/src → src}/eslint/configs/config.typescript.js +17 -1
- package/packages/biome/LICENSE +0 -201
- package/packages/biome/biome.json +0 -5
- package/packages/biome/configs/biome.common.json +0 -44
- package/packages/biome/configs/biome.next.json +0 -10
- package/packages/biome/configs/biome.react.json +0 -12
- package/packages/biome/configs/biome.svelte.json +0 -23
- package/packages/biome/docs/README.md +0 -29
- package/packages/eslint/docs/RULE_MAPPING.md +0 -50
- package/packages/eslint/scripts/init-common.sh +0 -4
- package/packages/eslint/scripts/init-next.sh +0 -4
- package/packages/eslint/scripts/init-react.sh +0 -4
- package/packages/eslint/scripts/init-svelte.sh +0 -4
- package/packages/eslint/scripts/linter-init.mjs +0 -209
- package/packages/eslint/scripts/remove-current.sh +0 -12
- /package/{packages/eslint/src → src}/eslint/configs/config.common.js +0 -0
- /package/{packages/eslint/src → src}/eslint/configs/config.gitignore.js +0 -0
- /package/{packages/eslint/src → src}/eslint/configs/config.import.js +0 -0
- /package/{packages/eslint/src → src}/eslint/configs/config.next.js +0 -0
- /package/{packages/eslint/src → src}/eslint/configs/config.prettier.js +0 -0
- /package/{packages/eslint/src → src}/eslint/configs/config.react.js +0 -0
- /package/{packages/eslint/src → src}/eslint/configs/config.sonarjs.js +0 -0
- /package/{packages/eslint/src → src}/eslint/configs/config.unicorn.js +0 -0
- /package/{packages/eslint/src → src}/eslint/configs/constants.js +0 -0
- /package/{packages/eslint/src → src}/eslint/configs/svelte-stack.js +0 -0
- /package/{packages/eslint/src → src}/eslint/eslint-svelte.config.js +0 -0
- /package/{packages/eslint/src → src}/eslint/eslint.config.common.js +0 -0
- /package/{packages/eslint/src → src}/eslint/eslint.config.next.js +0 -0
- /package/{packages/eslint/src → src}/eslint/eslint.config.react.js +0 -0
- /package/{packages/eslint/src → src}/examples/example.scss +0 -0
- /package/{packages/eslint/src → src}/examples/example.svelte +0 -0
- /package/{packages/eslint/src → src}/examples/example.ts +0 -0
- /package/{packages/eslint/src → src}/index.js +0 -0
- /package/{packages/eslint/src → src}/prettier/prettier-svelte.config.js +0 -0
- /package/{packages/eslint/src → src}/prettier/prettier.config.common.js +0 -0
- /package/{packages/eslint/src → src}/stylelint/stylelint.config.scss.js +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// Shared logic for @st1ggy/linter-config CLI (no prompts).
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from 'node:child_process'
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
|
|
7
|
+
export const PACKAGE = '@st1ggy/linter-config'
|
|
8
|
+
|
|
9
|
+
export const STACK_KEYS = ['common', 'react', 'next', 'svelte']
|
|
10
|
+
|
|
11
|
+
export const STACKS = {
|
|
12
|
+
common: {
|
|
13
|
+
eslint: 'eslint-common',
|
|
14
|
+
prettier: 'prettier-common',
|
|
15
|
+
stylelint: 'stylelint-scss',
|
|
16
|
+
},
|
|
17
|
+
react: {
|
|
18
|
+
eslint: 'eslint-react',
|
|
19
|
+
prettier: 'prettier-common',
|
|
20
|
+
stylelint: 'stylelint-scss',
|
|
21
|
+
},
|
|
22
|
+
next: {
|
|
23
|
+
eslint: 'eslint-next',
|
|
24
|
+
prettier: 'prettier-common',
|
|
25
|
+
stylelint: 'stylelint-scss',
|
|
26
|
+
},
|
|
27
|
+
svelte: {
|
|
28
|
+
eslint: 'eslint-svelte',
|
|
29
|
+
prettier: 'prettier-svelte',
|
|
30
|
+
stylelint: 'stylelint-scss',
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const STACK_CHOICES = [
|
|
35
|
+
{
|
|
36
|
+
value: 'common',
|
|
37
|
+
name: 'common — TypeScript/JS base + Stylelint (default)',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
value: 'react',
|
|
41
|
+
name: 'react — React + hooks on top of common',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
value: 'next',
|
|
45
|
+
name: 'next — Next.js App Router on top of react',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
value: 'svelte',
|
|
49
|
+
name: 'svelte — Svelte + Prettier plugin for Svelte',
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
export function readPackageJson(directory) {
|
|
54
|
+
const filePath = path.join(directory, 'package.json')
|
|
55
|
+
|
|
56
|
+
if (!existsSync(filePath)) {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(readFileSync(filePath, 'utf8'))
|
|
62
|
+
} catch {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function mergedDeps(packageJson) {
|
|
68
|
+
return {
|
|
69
|
+
...packageJson.optionalDependencies,
|
|
70
|
+
...packageJson.peerDependencies,
|
|
71
|
+
...packageJson.dependencies,
|
|
72
|
+
...packageJson.devDependencies,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function isSelfPackageRoot(directory) {
|
|
77
|
+
const packageJson = readPackageJson(directory)
|
|
78
|
+
|
|
79
|
+
return packageJson?.name === PACKAGE
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function hasResolvablePackage(startDirectory) {
|
|
83
|
+
let currentDirectory = path.resolve(startDirectory)
|
|
84
|
+
|
|
85
|
+
while (true) {
|
|
86
|
+
const marker = path.join(currentDirectory, 'node_modules', '@st1ggy', 'linter-config', 'package.json')
|
|
87
|
+
|
|
88
|
+
if (existsSync(marker)) {
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const parentDirectory = path.dirname(currentDirectory)
|
|
93
|
+
|
|
94
|
+
if (parentDirectory === currentDirectory) {
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
currentDirectory = parentDirectory
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function detectPackageManager(startDirectory) {
|
|
105
|
+
let currentDirectory = path.resolve(startDirectory)
|
|
106
|
+
|
|
107
|
+
while (true) {
|
|
108
|
+
if (existsSync(path.join(currentDirectory, 'pnpm-lock.yaml'))) {
|
|
109
|
+
return 'pnpm'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (existsSync(path.join(currentDirectory, 'yarn.lock'))) {
|
|
113
|
+
return 'yarn'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (existsSync(path.join(currentDirectory, 'bun.lock'))) {
|
|
117
|
+
return 'bun'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (existsSync(path.join(currentDirectory, 'package-lock.json'))) {
|
|
121
|
+
return 'npm'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const parentDirectory = path.dirname(currentDirectory)
|
|
125
|
+
|
|
126
|
+
if (parentDirectory === currentDirectory) {
|
|
127
|
+
return 'npm'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
currentDirectory = parentDirectory
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function runPmSync(command, args, cwd) {
|
|
135
|
+
const result = spawnSync(command, args, { cwd, env: process.env, stdio: 'inherit' })
|
|
136
|
+
|
|
137
|
+
if (result.error) {
|
|
138
|
+
throw result.error
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (result.status !== 0) {
|
|
142
|
+
throw new Error(`${command} ${args.join(' ')} exited with ${result.status}`)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// skipInstall: skip npm install; options.quiet: suppress stderr warnings
|
|
147
|
+
export function ensureDevDependency(targetDirectory, skipInstall, options = {}) {
|
|
148
|
+
const { quiet = false } = options
|
|
149
|
+
|
|
150
|
+
if (skipInstall) {
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const directory = path.resolve(targetDirectory)
|
|
155
|
+
|
|
156
|
+
const packageJsonPath = path.join(directory, 'package.json')
|
|
157
|
+
|
|
158
|
+
if (!existsSync(packageJsonPath)) {
|
|
159
|
+
if (!quiet) {
|
|
160
|
+
process.stderr.write(
|
|
161
|
+
`warning: no package.json in ${directory}; add the dependency manually: npm i -D ${PACKAGE}\n`,
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (hasResolvablePackage(directory)) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const packageJson = readPackageJson(directory)
|
|
173
|
+
|
|
174
|
+
if (!packageJson) {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const pm = detectPackageManager(directory)
|
|
179
|
+
const listed = Boolean(mergedDeps(packageJson)[PACKAGE])
|
|
180
|
+
|
|
181
|
+
if (listed) {
|
|
182
|
+
if (!quiet) {
|
|
183
|
+
process.stdout.write(`install: ${pm} install (${directory}) — dependency already listed\n`)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const sync = {
|
|
187
|
+
npm: () => runPmSync('npm', ['install'], directory),
|
|
188
|
+
pnpm: () => runPmSync('pnpm', ['install'], directory),
|
|
189
|
+
yarn: () => runPmSync('yarn', ['install'], directory),
|
|
190
|
+
bun: () => runPmSync('bun', ['install'], directory),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
sync[pm]()
|
|
194
|
+
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!quiet) {
|
|
199
|
+
process.stdout.write(`install: ${pm} add -D ${PACKAGE} (${directory})\n`)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const add = {
|
|
203
|
+
npm: () => runPmSync('npm', ['install', '-D', PACKAGE], directory),
|
|
204
|
+
pnpm: () => runPmSync('pnpm', ['add', '-D', PACKAGE], directory),
|
|
205
|
+
yarn: () => runPmSync('yarn', ['add', '-D', PACKAGE], directory),
|
|
206
|
+
bun: () => runPmSync('bun', ['add', '-d', PACKAGE], directory),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
add[pm]()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function printHelp() {
|
|
213
|
+
process.stdout.write(`\
|
|
214
|
+
${PACKAGE} — generate local wrapper configs in a consumer project.
|
|
215
|
+
|
|
216
|
+
With no command in an interactive terminal, opens a guided menu (or pass -i / --interactive).
|
|
217
|
+
|
|
218
|
+
Commands:
|
|
219
|
+
init Create missing files only (skip existing).
|
|
220
|
+
reinit Overwrite selected files.
|
|
221
|
+
create Same as init.
|
|
222
|
+
|
|
223
|
+
Writes (each command):
|
|
224
|
+
eslint.config.js, prettier.config.js, stylelint.config.js — re-exports for one stack.
|
|
225
|
+
|
|
226
|
+
Unless --skip-install: if package.json exists and ${PACKAGE} is not yet resolvable from node_modules
|
|
227
|
+
(walking up to the filesystem root), runs the detected package manager to add or sync the dependency.
|
|
228
|
+
|
|
229
|
+
Stack (at most one; default: common):
|
|
230
|
+
--common | --react | --next | --svelte
|
|
231
|
+
|
|
232
|
+
Options:
|
|
233
|
+
--dir, -d Target directory (default: current working directory).
|
|
234
|
+
--skip-install Do not run npm/pnpm/yarn/bun (only write wrapper files).
|
|
235
|
+
-i, --interactive Open the menu even when a command is passed (experimental).
|
|
236
|
+
|
|
237
|
+
Examples (after: npm i -D ${PACKAGE}):
|
|
238
|
+
npx ${PACKAGE}
|
|
239
|
+
npx ${PACKAGE} init
|
|
240
|
+
npx ${PACKAGE} init --react
|
|
241
|
+
npx ${PACKAGE} reinit --svelte --dir ./apps/web
|
|
242
|
+
npm exec ${PACKAGE} -- init --common
|
|
243
|
+
|
|
244
|
+
Without prior install (downloads this package; then runs the same CLI):
|
|
245
|
+
npx --yes ${PACKAGE} init --common
|
|
246
|
+
|
|
247
|
+
Repo (paths from root):
|
|
248
|
+
node scripts/linter-init.mjs init --common
|
|
249
|
+
`)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function jsReExport(subpath) {
|
|
253
|
+
return `export { default } from '${PACKAGE}/${subpath}';\n`
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function resolveTargetDirectory(raw) {
|
|
257
|
+
return path.resolve(raw)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function writeFile(targetDirectory, name, content, overwrite, options = {}) {
|
|
261
|
+
const { quiet = false } = options
|
|
262
|
+
const filePath = path.join(targetDirectory, name)
|
|
263
|
+
|
|
264
|
+
if (!overwrite && existsSync(filePath)) {
|
|
265
|
+
if (!quiet) {
|
|
266
|
+
process.stdout.write(`skip (exists): ${name}\n`)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return false
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
mkdirSync(targetDirectory, { recursive: true })
|
|
273
|
+
writeFileSync(filePath, content, 'utf8')
|
|
274
|
+
|
|
275
|
+
if (!quiet) {
|
|
276
|
+
process.stdout.write(`write: ${name}\n`)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return true
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function resolveStackKey(values) {
|
|
283
|
+
const chosen = []
|
|
284
|
+
|
|
285
|
+
for (const key of STACK_KEYS) {
|
|
286
|
+
if (values[key] === true) {
|
|
287
|
+
chosen.push(key)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (chosen.length === 0) {
|
|
292
|
+
return 'common'
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (chosen.length > 1) {
|
|
296
|
+
const flags = STACK_KEYS.map((key) => `--${key}`).join(', ')
|
|
297
|
+
|
|
298
|
+
throw new Error(`Pick at most one stack flag: ${flags}`)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return chosen[0]
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function run(mode, targetDirectory, stackKey, options = {}) {
|
|
305
|
+
const { quiet = false } = options
|
|
306
|
+
const stack = STACKS[stackKey]
|
|
307
|
+
|
|
308
|
+
if (!stack) {
|
|
309
|
+
throw new Error(`Unknown stack "${stackKey}". Use: ${STACK_KEYS.join(', ')}`)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const overwrite = mode === 'reinit'
|
|
313
|
+
|
|
314
|
+
writeFile(targetDirectory, 'eslint.config.js', jsReExport(stack.eslint), overwrite, { quiet })
|
|
315
|
+
writeFile(targetDirectory, 'prettier.config.js', jsReExport(stack.prettier), overwrite, { quiet })
|
|
316
|
+
writeFile(targetDirectory, 'stylelint.config.js', jsReExport(stack.stylelint), overwrite, { quiet })
|
|
317
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @st1ggy/linter-config — interactive menu (TTY) or minimist CLI.
|
|
3
|
+
|
|
4
|
+
import { CancelPromptError, ExitPromptError } from '@inquirer/core'
|
|
5
|
+
import { confirm, input, select } from '@inquirer/prompts'
|
|
6
|
+
import minimist from 'minimist'
|
|
7
|
+
import process from 'node:process'
|
|
8
|
+
import ora from 'ora'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
PACKAGE,
|
|
12
|
+
STACK_CHOICES,
|
|
13
|
+
ensureDevDependency,
|
|
14
|
+
isSelfPackageRoot,
|
|
15
|
+
printHelp,
|
|
16
|
+
resolveStackKey,
|
|
17
|
+
resolveTargetDirectory,
|
|
18
|
+
run,
|
|
19
|
+
} from './linter-init-core.mjs'
|
|
20
|
+
|
|
21
|
+
function parseArgv(argv) {
|
|
22
|
+
return minimist(argv, {
|
|
23
|
+
string: ['dir', 'd'],
|
|
24
|
+
boolean: ['help', 'h', 'skip-install', 'eslint', 'biome', 'common', 'react', 'next', 'svelte', 'interactive', 'i'],
|
|
25
|
+
alias: {
|
|
26
|
+
h: 'help',
|
|
27
|
+
d: 'dir',
|
|
28
|
+
i: 'interactive',
|
|
29
|
+
},
|
|
30
|
+
default: {},
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function argvToValues(argv) {
|
|
35
|
+
const directory = argv.dir ?? argv.d ?? process.cwd()
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
dir: directory,
|
|
39
|
+
'skip-install': Boolean(argv['skip-install']),
|
|
40
|
+
eslint: Boolean(argv.eslint),
|
|
41
|
+
biome: Boolean(argv.biome),
|
|
42
|
+
common: Boolean(argv.common),
|
|
43
|
+
react: Boolean(argv.react),
|
|
44
|
+
next: Boolean(argv.next),
|
|
45
|
+
svelte: Boolean(argv.svelte),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function runInteractive() {
|
|
50
|
+
const command = await select({
|
|
51
|
+
message: 'Command',
|
|
52
|
+
choices: [
|
|
53
|
+
{
|
|
54
|
+
value: 'init',
|
|
55
|
+
name: 'init — create eslint / prettier / stylelint wrappers (skip if files exist)',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
value: 'reinit',
|
|
59
|
+
name: 'reinit — overwrite the three wrapper files',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
value: 'create',
|
|
63
|
+
name: 'create — same as init',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
default: 'init',
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const stackKey = await select({
|
|
70
|
+
message: 'Stack',
|
|
71
|
+
choices: STACK_CHOICES,
|
|
72
|
+
default: 'common',
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const targetDirectory = await input({
|
|
76
|
+
message: 'Target directory',
|
|
77
|
+
default: process.cwd(),
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const skipInstall = await confirm({
|
|
81
|
+
message: `Skip installing ${PACKAGE} (only write config files)?`,
|
|
82
|
+
default: false,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const resolved = resolveTargetDirectory(targetDirectory)
|
|
86
|
+
|
|
87
|
+
process.stdout.write('\n')
|
|
88
|
+
process.stdout.write(` Command: ${command}\n`)
|
|
89
|
+
process.stdout.write(` Stack: ${stackKey}\n`)
|
|
90
|
+
process.stdout.write(` Dir: ${resolved}\n`)
|
|
91
|
+
process.stdout.write(` Skip install: ${skipInstall ? 'yes' : 'no'}\n`)
|
|
92
|
+
process.stdout.write('\n')
|
|
93
|
+
|
|
94
|
+
const proceed = await confirm({
|
|
95
|
+
message: 'Proceed?',
|
|
96
|
+
default: true,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
if (!proceed) {
|
|
100
|
+
process.stdout.write('Cancelled.\n')
|
|
101
|
+
process.exitCode = 0
|
|
102
|
+
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (isSelfPackageRoot(resolved)) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Refusing to write consumer wrapper configs in the ${PACKAGE} source tree (that would replace this repository’s dev configs). Run from another project, or pass a different directory.`,
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const cmd = command === 'create' ? 'init' : command
|
|
113
|
+
|
|
114
|
+
if (skipInstall) {
|
|
115
|
+
ora({ color: 'gray' }).info(`Skipped installing ${PACKAGE} (--skip-install)`)
|
|
116
|
+
} else {
|
|
117
|
+
const spinner = ora({
|
|
118
|
+
color: 'cyan',
|
|
119
|
+
text: `Ensuring ${PACKAGE} is installed…`,
|
|
120
|
+
}).start()
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
ensureDevDependency(resolved, false, { quiet: true })
|
|
124
|
+
spinner.succeed(`${PACKAGE} is available`)
|
|
125
|
+
} catch (error) {
|
|
126
|
+
spinner.fail('Dependency step failed')
|
|
127
|
+
throw error
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const writeSpinner = ora({
|
|
132
|
+
color: 'green',
|
|
133
|
+
text: 'Writing eslint.config.js, prettier.config.js, stylelint.config.js…',
|
|
134
|
+
}).start()
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
run(cmd, resolved, stackKey, { quiet: true })
|
|
138
|
+
writeSpinner.succeed('Wrapper configs written')
|
|
139
|
+
} catch (error) {
|
|
140
|
+
writeSpinner.fail('Failed to write configs')
|
|
141
|
+
throw error
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function runCli(command, values) {
|
|
146
|
+
if (values.biome === true) {
|
|
147
|
+
process.stderr.write(
|
|
148
|
+
`warning: --biome is ignored; Biome presets were removed from ${PACKAGE}. Only ESLint, Prettier, and Stylelint wrappers are written.\n`,
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const targetDirectory = resolveTargetDirectory(values.dir)
|
|
153
|
+
const stackKey = resolveStackKey(values)
|
|
154
|
+
const skipInstall = values['skip-install'] === true
|
|
155
|
+
|
|
156
|
+
if (isSelfPackageRoot(targetDirectory)) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Refusing to write consumer wrapper configs in the ${PACKAGE} source tree (that would replace this repository’s dev configs). Run from another project, or pass --dir <path> to a folder with its own package.json.`,
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const cmd = command === 'create' ? 'init' : command
|
|
163
|
+
|
|
164
|
+
ensureDevDependency(targetDirectory, skipInstall)
|
|
165
|
+
run(cmd, targetDirectory, stackKey)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function runInteractiveOrHandleErrors() {
|
|
169
|
+
try {
|
|
170
|
+
await runInteractive()
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (error instanceof ExitPromptError || error instanceof CancelPromptError) {
|
|
173
|
+
process.stdout.write('\nCancelled.\n')
|
|
174
|
+
process.exitCode = 0
|
|
175
|
+
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
180
|
+
|
|
181
|
+
process.stderr.write(`${message}\n`)
|
|
182
|
+
process.exitCode = 1
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function runSubcommandCli(command, argv) {
|
|
187
|
+
const values = argvToValues(argv)
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
runCli(command, values)
|
|
191
|
+
} catch (error) {
|
|
192
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
193
|
+
|
|
194
|
+
process.stderr.write(`${message}\n`)
|
|
195
|
+
process.exitCode = 1
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function main() {
|
|
200
|
+
const raw = process.argv.slice(2)
|
|
201
|
+
const argv = parseArgv(raw)
|
|
202
|
+
const positional = argv._
|
|
203
|
+
const first = positional[0]
|
|
204
|
+
|
|
205
|
+
if (argv.help) {
|
|
206
|
+
printHelp()
|
|
207
|
+
process.exitCode = 0
|
|
208
|
+
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const tty = Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
213
|
+
const forceMenu = Boolean(argv.interactive || argv.i)
|
|
214
|
+
const noSubcommand = positional.length === 0
|
|
215
|
+
const useInteractiveMenu = noSubcommand && (forceMenu || tty)
|
|
216
|
+
|
|
217
|
+
if (useInteractiveMenu) {
|
|
218
|
+
await runInteractiveOrHandleErrors()
|
|
219
|
+
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (noSubcommand) {
|
|
224
|
+
printHelp()
|
|
225
|
+
process.exitCode = 1
|
|
226
|
+
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const command = first
|
|
231
|
+
|
|
232
|
+
if (command !== 'init' && command !== 'reinit' && command !== 'create') {
|
|
233
|
+
process.stderr.write(`Unknown command "${String(command)}".\n\n`)
|
|
234
|
+
printHelp()
|
|
235
|
+
process.exitCode = 1
|
|
236
|
+
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
runSubcommandCli(command, argv)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
await main()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Optional migration helper: interactively delete files in the *current directory* whose names
|
|
3
|
+
# contain eslint, stylelint, or prettier (maxdepth 1). Use before switching to the three standard
|
|
4
|
+
# wrappers from @st1ggy/linter-config — it does NOT replace `reinit` (reinit overwrites the standard
|
|
5
|
+
# filenames without prompts). Not invoked by the CLI; run manually when cleaning legacy configs.
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
for tool in eslint stylelint prettier; do
|
|
10
|
+
while IFS= read -r -d '' file; do
|
|
11
|
+
read -r -p "Delete ${file#./}? y/n " -n 1 -r
|
|
12
|
+
echo
|
|
13
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
14
|
+
rm -f "$file"
|
|
15
|
+
fi
|
|
16
|
+
done < <(find . -maxdepth 1 -type f -name "*${tool}*" -print0)
|
|
17
|
+
done
|
|
@@ -3,9 +3,25 @@ import { configs } from 'typescript-eslint'
|
|
|
3
3
|
|
|
4
4
|
import { filesTypeAware } from './constants.js'
|
|
5
5
|
|
|
6
|
+
// `flat/stylistic` enables `@typescript-eslint/consistent-type-definitions` as `'error'` only, which
|
|
7
|
+
// uses the rule default (`interface`). Strip it here so we set `type` explicitly below.
|
|
8
|
+
const stylisticWithoutTypeDefinitionStyle = configs.stylistic.map((block) => {
|
|
9
|
+
const rules = block.rules
|
|
10
|
+
|
|
11
|
+
if (!rules?.['@typescript-eslint/consistent-type-definitions']) {
|
|
12
|
+
return block
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const nextRules = { ...rules }
|
|
16
|
+
|
|
17
|
+
delete nextRules['@typescript-eslint/consistent-type-definitions']
|
|
18
|
+
|
|
19
|
+
return { ...block, rules: nextRules }
|
|
20
|
+
})
|
|
21
|
+
|
|
6
22
|
export default defineConfig([
|
|
7
23
|
...configs.recommended,
|
|
8
|
-
...
|
|
24
|
+
...stylisticWithoutTypeDefinitionStyle,
|
|
9
25
|
{
|
|
10
26
|
files: filesTypeAware,
|
|
11
27
|
languageOptions: {
|