@st1ggy/linter-config 6.3.2 → 7.2.2

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.
@@ -10,18 +10,36 @@ import ora from 'ora'
10
10
  import {
11
11
  PACKAGE,
12
12
  STACK_CHOICES,
13
- ensureDevDependency,
13
+ STACK_KEYS,
14
+ ensureDevDependencies,
15
+ existingWrapperFiles,
14
16
  isSelfPackageRoot,
17
+ legacyConfigFiles,
15
18
  printHelp,
19
+ removeFiles,
16
20
  resolveStackKey,
17
21
  resolveTargetDirectory,
18
22
  run,
23
+ stackPackages,
19
24
  } from './linter-init-core.mjs'
20
25
 
21
26
  function parseArgv(argv) {
22
27
  return minimist(argv, {
23
28
  string: ['dir', 'd'],
24
- boolean: ['help', 'h', 'skip-install', 'eslint', 'biome', 'common', 'react', 'next', 'svelte', 'interactive', 'i'],
29
+ boolean: [
30
+ 'help',
31
+ 'h',
32
+ 'skip-install',
33
+ 'eslint',
34
+ 'biome',
35
+ 'common',
36
+ 'react',
37
+ 'next',
38
+ 'svelte',
39
+ 'astro',
40
+ 'interactive',
41
+ 'i',
42
+ ],
25
43
  alias: {
26
44
  h: 'help',
27
45
  d: 'dir',
@@ -43,85 +61,120 @@ function argvToValues(argv) {
43
61
  react: Boolean(argv.react),
44
62
  next: Boolean(argv.next),
45
63
  svelte: Boolean(argv.svelte),
64
+ astro: Boolean(argv.astro),
46
65
  }
47
66
  }
48
67
 
49
- async function runInteractive() {
50
- const command = await select({
51
- message: 'Command',
68
+ async function selectCommand(command) {
69
+ if (command) {
70
+ return command
71
+ }
72
+
73
+ return select({
74
+ message: 'Action',
52
75
  choices: [
53
76
  {
54
77
  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',
78
+ name: 'init — create missing ESLint, Prettier and Stylelint wrappers',
60
79
  },
61
80
  {
62
- value: 'create',
63
- name: 'create — same as init',
81
+ value: 'migrate',
82
+ name: 'migrate — remove selected legacy configs and replace wrappers',
64
83
  },
65
84
  ],
66
85
  default: 'init',
67
86
  })
87
+ }
88
+
89
+ async function selectStack(values) {
90
+ const stackKey = resolveStackKey(values)
91
+
92
+ if (STACK_KEYS.some((key) => values[key] === true)) {
93
+ return stackKey
94
+ }
68
95
 
69
- const stackKey = await select({
96
+ return select({
70
97
  message: 'Stack',
71
98
  choices: STACK_CHOICES,
72
99
  default: 'common',
73
100
  })
101
+ }
74
102
 
75
- const targetDirectory = await input({
76
- message: 'Target directory',
77
- default: process.cwd(),
78
- })
103
+ async function selectLegacyFiles(command, directory) {
104
+ if (command !== 'migrate') {
105
+ return []
106
+ }
79
107
 
80
- const skipInstall = await confirm({
81
- message: `Skip installing ${PACKAGE} (only write config files)?`,
82
- default: false,
83
- })
108
+ const selected = []
84
109
 
85
- const resolved = resolveTargetDirectory(targetDirectory)
110
+ for (const file of legacyConfigFiles(directory)) {
111
+ if (await confirm({ message: `Remove legacy config ${file}?`, default: false })) {
112
+ selected.push(file)
113
+ }
114
+ }
115
+
116
+ return selected
117
+ }
118
+
119
+ function printSummary(command, stack, directory, shouldSkipInstall, existing, filesToRemove, packages) {
120
+ const wrapperFiles =
121
+ command === 'init' && existing.length > 0
122
+ ? `create missing; keep ${existing.join(', ')}`
123
+ : 'write eslint.config.js, prettier.config.js, stylelint.config.js'
86
124
 
87
125
  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`)
126
+ process.stdout.write(` Action: ${command}\n`)
127
+ process.stdout.write(` Stack: ${stack}\n`)
128
+ process.stdout.write(` Dir: ${directory}\n`)
129
+ process.stdout.write(` Install packages: ${shouldSkipInstall ? 'no (--skip-install)' : packages.join(', ')}\n`)
130
+ process.stdout.write(` Wrapper files: ${wrapperFiles}\n`)
131
+ process.stdout.write(` Legacy files to remove: ${filesToRemove.length > 0 ? filesToRemove.join(', ') : 'none'}\n`)
92
132
  process.stdout.write('\n')
133
+ }
93
134
 
94
- const proceed = await confirm({
135
+ async function runInteractive(command, values) {
136
+ const selectedCommand = await selectCommand(command)
137
+ const selectedStack = await selectStack(values)
138
+ const targetDirectory = await input({ message: 'Target directory', default: values.dir ?? process.cwd() })
139
+
140
+ const resolved = resolveTargetDirectory(targetDirectory)
141
+
142
+ if (isSelfPackageRoot(resolved)) {
143
+ throw new Error(
144
+ `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.`,
145
+ )
146
+ }
147
+
148
+ const shouldSkipInstall = values['skip-install'] === true
149
+ const existing = existingWrapperFiles(resolved)
150
+ const filesToRemove = await selectLegacyFiles(selectedCommand, resolved)
151
+ const packages = stackPackages(selectedStack)
152
+
153
+ printSummary(selectedCommand, selectedStack, resolved, shouldSkipInstall, existing, filesToRemove, packages)
154
+
155
+ const shouldProceed = await confirm({
95
156
  message: 'Proceed?',
96
157
  default: true,
97
158
  })
98
159
 
99
- if (!proceed) {
160
+ if (!shouldProceed) {
100
161
  process.stdout.write('Cancelled.\n')
101
162
  process.exitCode = 0
102
163
 
103
164
  return
104
165
  }
105
166
 
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) {
167
+ if (shouldSkipInstall) {
115
168
  ora({ color: 'gray' }).info(`Skipped installing ${PACKAGE} (--skip-install)`)
116
169
  } else {
117
170
  const spinner = ora({
118
171
  color: 'cyan',
119
- text: `Ensuring ${PACKAGE} is installed…`,
172
+ text: `Installing ${selectedStack} linting packages…`,
120
173
  }).start()
121
174
 
122
175
  try {
123
- ensureDevDependency(resolved, false, { quiet: true })
124
- spinner.succeed(`${PACKAGE} is available`)
176
+ ensureDevDependencies(resolved, packages, false, { quiet: true })
177
+ spinner.succeed(`${selectedStack} linting packages are available`)
125
178
  } catch (error) {
126
179
  spinner.fail('Dependency step failed')
127
180
  throw error
@@ -134,7 +187,8 @@ async function runInteractive() {
134
187
  }).start()
135
188
 
136
189
  try {
137
- run(cmd, resolved, stackKey, { quiet: true })
190
+ removeFiles(resolved, filesToRemove)
191
+ run(selectedCommand, resolved, selectedStack, { quiet: true })
138
192
  writeSpinner.succeed('Wrapper configs written')
139
193
  } catch (error) {
140
194
  writeSpinner.fail('Failed to write configs')
@@ -142,32 +196,30 @@ async function runInteractive() {
142
196
  }
143
197
  }
144
198
 
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
- )
199
+ function normalizeCommand(command) {
200
+ if (command === 'create') {
201
+ return 'init'
150
202
  }
151
203
 
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
- )
204
+ if (command === 'reinit') {
205
+ return 'migrate'
160
206
  }
161
207
 
162
- const cmd = command === 'create' ? 'init' : command
208
+ return command
209
+ }
163
210
 
164
- ensureDevDependency(targetDirectory, skipInstall)
165
- run(cmd, targetDirectory, stackKey)
211
+ function warnLegacyFlags(values) {
212
+ if (values.biome === true) {
213
+ process.stderr.write(
214
+ `warning: --biome is ignored; Biome presets were removed from ${PACKAGE}. Only ESLint, Prettier, and Stylelint wrappers are written.\n`,
215
+ )
216
+ }
166
217
  }
167
218
 
168
- async function runInteractiveOrHandleErrors() {
219
+ async function runInteractiveOrHandleErrors(command, values) {
169
220
  try {
170
- await runInteractive()
221
+ warnLegacyFlags(values)
222
+ await runInteractive(command, values)
171
223
  } catch (error) {
172
224
  if (error instanceof ExitPromptError || error instanceof CancelPromptError) {
173
225
  process.stdout.write('\nCancelled.\n')
@@ -183,24 +235,8 @@ async function runInteractiveOrHandleErrors() {
183
235
  }
184
236
  }
185
237
 
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
238
  async function main() {
200
- const raw = process.argv.slice(2)
201
- const argv = parseArgv(raw)
202
- const positional = argv._
203
- const first = positional[0]
239
+ const argv = parseArgv(process.argv.slice(2))
204
240
 
205
241
  if (argv.help) {
206
242
  printHelp()
@@ -209,35 +245,24 @@ async function main() {
209
245
  return
210
246
  }
211
247
 
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()
248
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
249
+ process.stderr.write('This command requires an interactive terminal.\n')
225
250
  process.exitCode = 1
226
251
 
227
252
  return
228
253
  }
229
254
 
230
- const command = first
255
+ const first = argv._[0]
231
256
 
232
- if (command !== 'init' && command !== 'reinit' && command !== 'create') {
233
- process.stderr.write(`Unknown command "${String(command)}".\n\n`)
257
+ if (first !== undefined && first !== 'init' && first !== 'migrate' && first !== 'reinit' && first !== 'create') {
258
+ process.stderr.write(`Unknown command "${String(first)}".\n\n`)
234
259
  printHelp()
235
260
  process.exitCode = 1
236
261
 
237
262
  return
238
263
  }
239
264
 
240
- runSubcommandCli(command, argv)
265
+ await runInteractiveOrHandleErrors(first === undefined ? undefined : normalizeCommand(first), argvToValues(argv))
241
266
  }
242
267
 
243
268
  await main()
@@ -0,0 +1,3 @@
1
+ import { configs } from 'eslint-plugin-astro'
2
+
3
+ export default configs.recommended
@@ -102,7 +102,7 @@ export default defineConfig([
102
102
  { blankLine: 'always', prev: '*', next: 'case' },
103
103
  { blankLine: 'always', prev: 'if', next: '*' },
104
104
  ],
105
- '@stylistic/multiline-comment-style': ['error', 'separate-lines', { checkJSDoc: true }],
105
+ '@stylistic/multiline-comment-style': 'off',
106
106
  },
107
107
  },
108
108
  ])
@@ -1,11 +1,10 @@
1
- import { includeIgnoreFile } from '@eslint/compat'
1
+ import { includeIgnoreFile } from '@eslint/config-helpers'
2
2
  import { defineConfig } from 'eslint/config'
3
3
  import { findUpSync } from 'find-up-simple'
4
4
  import process from 'node:process'
5
5
  import { fileURLToPath } from 'node:url'
6
6
 
7
7
  const gitignorePath = findUpSync('.gitignore', { cwd: process.cwd() })
8
+ const ignoreFile = gitignorePath ? fileURLToPath(new URL(gitignorePath, import.meta.url)) : null
8
9
 
9
- export default defineConfig(
10
- gitignorePath ? [includeIgnoreFile(fileURLToPath(new URL(gitignorePath, import.meta.url)))] : [],
11
- )
10
+ export default defineConfig(ignoreFile ? [includeIgnoreFile(ignoreFile)] : [])
@@ -12,7 +12,7 @@ export default defineConfig([
12
12
  'unicorn/custom-error-definition': 'error',
13
13
  'unicorn/no-unused-properties': 'error',
14
14
  'unicorn/no-null': 'off',
15
- 'unicorn/prevent-abbreviations': [
15
+ 'unicorn/name-replacements': [
16
16
  'error',
17
17
  {
18
18
  replacements: {
@@ -1,7 +1,9 @@
1
1
  export const files = ['**/*.{js,jsx,ts,tsx,mts,cts,mjs,cjs,vue,svelte}']
2
2
 
3
- /** For `parserOptions.project` / type-aware @typescript-eslint rules (omit `.svelte`; Svelte scripts use svelte-eslint-parser). */
3
+ // For `parserOptions.project` / type-aware @typescript-eslint rules (omit `.svelte`; Svelte scripts use svelte-eslint-parser).
4
+ // Svelte scripts use svelte-eslint-parser.
4
5
  export const filesTypeAware = ['**/*.{js,jsx,ts,tsx,mts,cts,mjs,cjs,vue}']
5
6
 
6
- /** Prettier run via eslint-plugin-prettier (omit `.svelte` unless Prettier is configured with `prettier-plugin-svelte`). */
7
+ // Prettier runs via eslint-plugin-prettier.
8
+ // Omit `.svelte` unless Prettier is configured with `prettier-plugin-svelte`.
7
9
  export const filesPrettier = ['**/*.{js,jsx,ts,tsx,mts,cts,mjs,cjs,vue}']
@@ -0,0 +1,4 @@
1
+ import astroConfig from './configs/astro-stack.js'
2
+ import commonConfig from './eslint.config.common.js'
3
+
4
+ export default [...commonConfig, ...astroConfig]
@@ -0,0 +1,7 @@
1
+ ---
2
+ const title = 'Astro example'
3
+ ---
4
+
5
+ <main>
6
+ <h1>{title}</h1>
7
+ </main>
@@ -1 +1 @@
1
- export const example = true
1
+ export const isExample = true
package/src/index.js CHANGED
@@ -2,8 +2,10 @@ export { default as eslintCommon } from './eslint/eslint.config.common.js'
2
2
  export { default as eslintReact } from './eslint/eslint.config.react.js'
3
3
  export { default as eslintNext } from './eslint/eslint.config.next.js'
4
4
  export { default as eslintSvelte } from './eslint/eslint-svelte.config.js'
5
+ export { default as eslintAstro } from './eslint/eslint.config.astro.js'
5
6
 
6
7
  export { default as prettierCommon } from './prettier/prettier.config.common.js'
7
8
  export { default as prettierSvelte } from './prettier/prettier-svelte.config.js'
9
+ export { default as prettierAstro } from './prettier/prettier-astro.config.js'
8
10
 
9
11
  export { default as stylelintScss } from './stylelint/stylelint.config.scss.js'
@@ -0,0 +1,7 @@
1
+ import commonConfig from './prettier.config.common.js'
2
+
3
+ export default {
4
+ ...commonConfig,
5
+ plugins: ['prettier-plugin-astro'],
6
+ overrides: [{ files: '*.astro', options: { parser: 'astro' } }],
7
+ }