@kitql/eslint-config 0.5.3 → 0.5.5-next.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/cmd.js CHANGED
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from 'node:child_process'
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
3
5
  import { Option, program } from 'commander'
4
6
  import ora from 'ora'
5
7
 
6
- import { bgBlueBright, bgGreen, bgRedBright, gray, red } from '@kitql/helpers'
8
+ import { bgBlueBright, bgGreen, bgRedBright, gray, green, red } from '@kitql/helpers'
7
9
 
8
10
  import { findFileOrUp } from './helper/findFileOrUp.js'
9
11
 
12
+ // df
10
13
  const spinner = ora({
11
14
  // hideCursor: true,
12
15
  prefixText: bgBlueBright(` kitql-lint `),
@@ -16,7 +19,15 @@ spinner.start()
16
19
 
17
20
  program.addOption(new Option('-f, --format', 'format'))
18
21
  program.addOption(new Option('-g, --glob <type>', 'file/dir/glob (. by default)', '.'))
22
+ program.addOption(new Option('--eslint-only', 'only run eslint', false))
23
+ program.addOption(new Option('--prettier-only', 'only run prettier', false))
19
24
  program.addOption(new Option('--verbose', 'add more logs', false))
25
+ program.addOption(
26
+ new Option('-d, --diff-only', 'only check files changed against base branch', false),
27
+ )
28
+ program.addOption(
29
+ new Option('--base-branch <type>', 'base branch to compare against (default: main)', 'main'),
30
+ )
20
31
  program.addOption(
21
32
  new Option(
22
33
  '-p, --prefix <type>',
@@ -29,12 +40,16 @@ program.parse(process.argv)
29
40
  const options_cli = program.opts()
30
41
 
31
42
  const pathPrettierIgnore = findFileOrUp('.prettierignore')
32
- const pathPrettierCjs = findFileOrUp('.prettierrc.mjs')
43
+ const pathPrettierMjs = findFileOrUp('.prettierrc.mjs')
33
44
 
34
45
  const format = options_cli.format ?? false
35
- const glob = options_cli.glob ?? '.'
46
+ let glob = options_cli.glob ?? '.'
36
47
  const verbose = options_cli.verbose ?? false
37
48
  const pre = options_cli.prefix ?? 'none'
49
+ const eslintOnly = options_cli.eslintOnly ?? false
50
+ const prettierOnly = options_cli.prettierOnly ?? false
51
+ const diffOnly = options_cli.diffOnly ?? false
52
+ const baseBranch = options_cli.baseBranch ?? 'main'
38
53
 
39
54
  let preToUse = ''
40
55
  if (pre === 'npm') {
@@ -74,10 +89,191 @@ async function customSpawn(cmd) {
74
89
  return data
75
90
  }
76
91
 
92
+ let filesLength = -1
93
+ async function getDiffFiles() {
94
+ spinner.text = verbose
95
+ ? 'git diff ' + gray(`(getting changed files against ${baseBranch})`)
96
+ : 'git diff'
97
+
98
+ // First, get the git repository root
99
+ let gitRootPath = ''
100
+ try {
101
+ const gitRootCmd = 'git rev-parse --show-toplevel'
102
+ const gitRootChild = spawn(gitRootCmd, {
103
+ shell: true,
104
+ cwd: process.cwd(),
105
+ })
106
+
107
+ let rootData = ''
108
+ for await (const chunk of gitRootChild.stdout) {
109
+ rootData += chunk
110
+ }
111
+
112
+ const gitRootExitCode = await new Promise((resolve) => {
113
+ gitRootChild.on('close', resolve)
114
+ })
115
+
116
+ if (gitRootExitCode === 0) {
117
+ gitRootPath = rootData.trim()
118
+ } else {
119
+ spinner.warn('Could not determine git repository root')
120
+ return null
121
+ }
122
+ } catch (error) {
123
+ spinner.warn(`Error getting git root: ${error.message}`)
124
+ return null
125
+ }
126
+
127
+ // Try to find the best base branch to compare against
128
+ const possibleBranches = [baseBranch, 'main', 'HEAD~1']
129
+ let validBranch = null
130
+
131
+ for (const branch of possibleBranches) {
132
+ try {
133
+ // Check if the branch exists
134
+ const checkBranchCmd = `git rev-parse --verify ${branch}`
135
+ const checkBranchChild = spawn(checkBranchCmd, {
136
+ shell: true,
137
+ cwd: process.cwd(),
138
+ })
139
+
140
+ const branchExitCode = await new Promise((resolve) => {
141
+ checkBranchChild.on('close', resolve)
142
+ })
143
+
144
+ if (branchExitCode === 0) {
145
+ validBranch = branch
146
+ if (verbose && branch !== baseBranch) {
147
+ spinner.info(`Using '${branch}' as base branch instead of '${baseBranch}'`)
148
+ }
149
+ break
150
+ }
151
+ } catch (error) {
152
+ // Continue to next branch
153
+ }
154
+ }
155
+
156
+ if (!validBranch) {
157
+ // If in CI, try to use a different approach
158
+ if (process.env.CI) {
159
+ try {
160
+ // In CI, we can try to get all staged and unstaged changes
161
+ validBranch = 'HEAD'
162
+ if (verbose) {
163
+ spinner.info('In CI environment, checking all changes')
164
+ }
165
+ } catch (error) {
166
+ spinner.warn(`Could not find a valid base branch to compare against`)
167
+ return null
168
+ }
169
+ } else {
170
+ spinner.warn(`Could not find a valid base branch to compare against`)
171
+ return null
172
+ }
173
+ }
174
+
175
+ // Now get the changed files
176
+ const cmd = `git diff --name-only --diff-filter=ACMR ${validBranch}`
177
+
178
+ try {
179
+ const child = spawn(cmd, {
180
+ shell: true,
181
+ cwd: process.cwd(),
182
+ })
183
+
184
+ let data = ''
185
+ for await (const chunk of child.stdout) {
186
+ data += chunk
187
+ }
188
+
189
+ let error = ''
190
+ for await (const chunk of child.stderr) {
191
+ error += chunk
192
+ }
193
+
194
+ const exitCode = await new Promise((resolve) => {
195
+ child.on('close', resolve)
196
+ })
197
+
198
+ if (exitCode) {
199
+ // If the diff command failed, try a fallback approach for CI environments
200
+ if (process.env.CI) {
201
+ try {
202
+ // In CI, we can try to get all tracked files that have changes
203
+ const fallbackCmd = 'git ls-files --modified --others --exclude-standard'
204
+ const fallbackChild = spawn(fallbackCmd, {
205
+ shell: true,
206
+ cwd: process.cwd(),
207
+ })
208
+
209
+ let fallbackData = ''
210
+ for await (const chunk of fallbackChild.stdout) {
211
+ fallbackData += chunk
212
+ }
213
+
214
+ const fallbackExitCode = await new Promise((resolve) => {
215
+ fallbackChild.on('close', resolve)
216
+ })
217
+
218
+ if (fallbackExitCode === 0 && fallbackData.trim()) {
219
+ data = fallbackData
220
+ if (verbose) {
221
+ spinner.info('Using fallback method to get changed files in CI')
222
+ }
223
+ } else {
224
+ spinner.warn(`Could not get changed files: ${error}`)
225
+ return null
226
+ }
227
+ } catch (fallbackError) {
228
+ spinner.warn(`Could not get changed files: ${error}`)
229
+ return null
230
+ }
231
+ } else {
232
+ spinner.warn(`Could not get changed files: ${error}`)
233
+ return null
234
+ }
235
+ }
236
+
237
+ // Get the current working directory
238
+ const cwd = process.cwd()
239
+
240
+ // Process the files to make them relative to the current working directory
241
+ const files = data
242
+ .trim()
243
+ .split('\n')
244
+ .filter(Boolean)
245
+ .map((file) => {
246
+ // Convert the git path (relative to git root) to an absolute path
247
+ const absolutePath = path.join(gitRootPath, file)
248
+
249
+ // Convert the absolute path to a path relative to the current working directory
250
+ const relativePath = path.relative(cwd, absolutePath)
251
+
252
+ // Check if the file exists and is at or below the current directory
253
+ if (fs.existsSync(relativePath) && !relativePath.startsWith('..')) {
254
+ return relativePath
255
+ }
256
+ return null
257
+ })
258
+ .filter(Boolean) // Remove null entries (files not at or below current directory)
259
+
260
+ filesLength = files.length
261
+ if (verbose) {
262
+ spinner.info(`Found ${filesLength} changed files at or below current directory`)
263
+ }
264
+
265
+ // Format the files for the command line, wrapping each in quotes and joining with spaces
266
+ return files.length > 0 ? files.map((f) => `'${f}'`).join(' ') : null
267
+ } catch (error) {
268
+ spinner.warn(`Error getting changed files: ${error.message}`)
269
+ return null
270
+ }
271
+ }
272
+
77
273
  async function eslintRun() {
78
274
  const cmdEsLint =
79
275
  preToUse +
80
- `eslint` +
276
+ `eslint --no-warn-ignored` +
81
277
  // format or not
82
278
  `${format ? ' --fix' : ''}` +
83
279
  // exec
@@ -98,7 +294,7 @@ async function prettierRun() {
98
294
  // ignore?
99
295
  ` --ignore-path ${pathPrettierIgnore}` +
100
296
  // config
101
- ` --config ${pathPrettierCjs}` +
297
+ ` --config ${pathPrettierMjs}` +
102
298
  // format or not
103
299
  `${format ? ' --write' : ''}` +
104
300
  // exec
@@ -111,21 +307,54 @@ async function prettierRun() {
111
307
  return result_prettier
112
308
  }
113
309
 
114
- const eslintCode = await eslintRun()
115
- if (eslintCode.status) {
116
- spinner.prefixText = bgRedBright(` kitql-lint `)
117
- spinner.fail(red(`eslint failed, check logs above.`))
118
- process.exit(eslintCode.status)
310
+ const took = []
311
+
312
+ const display = (text, time) => {
313
+ return `${gray(text)} ${green((time / 1000).toFixed(3))}${gray('s')}`
119
314
  }
120
315
 
121
- const prettierCode = await prettierRun()
122
- if (prettierCode.status) {
123
- spinner.prefixText = bgRedBright(` kitql-lint `)
124
- spinner.fail(red(`prettier failed, check logs above.`))
125
- process.exit(prettierCode.status)
316
+ // If changed-only flag is set, get the list of changed files
317
+ if (diffOnly) {
318
+ spinner.text = 'Checking for changed files'
319
+ const changedFilesStart = performance.now()
320
+ const changedFiles = await getDiffFiles()
321
+ const changedFilesTook = performance.now() - changedFilesStart
322
+ took.push(display('diff', changedFilesTook))
323
+
324
+ if (changedFiles) {
325
+ glob = changedFiles
326
+ } else {
327
+ glob = ''
328
+ }
329
+ }
330
+
331
+ if (!prettierOnly && glob) {
332
+ const esLintStart = performance.now()
333
+ const eslintCode = await eslintRun()
334
+ const esLintTook = performance.now() - esLintStart
335
+ took.push(display('eslint', esLintTook))
336
+ if (eslintCode.status) {
337
+ spinner.prefixText = bgRedBright(` kitql-lint `)
338
+ spinner.fail(red(`eslint failed, check logs above.`))
339
+ process.exit(eslintCode.status)
340
+ }
341
+ }
342
+
343
+ if (!eslintOnly && glob) {
344
+ const prettierStart = performance.now()
345
+ const prettierCode = await prettierRun()
346
+ const prettierTook = performance.now() - prettierStart
347
+ took.push(display('prettier', prettierTook))
348
+ if (prettierCode.status) {
349
+ spinner.prefixText = bgRedBright(` kitql-lint `)
350
+ spinner.fail(red(`prettier failed, check logs above.`))
351
+ process.exit(prettierCode.status)
352
+ }
126
353
  }
127
354
 
128
355
  spinner.prefixText = bgGreen(` kitql-lint `)
129
- spinner.succeed(`All good, your files looks great!`)
356
+ spinner.succeed(
357
+ `All good, ${glob === '' ? 'nothing to do!' : filesLength !== -1 ? `your ${filesLength} files looks great!` : 'your files looks great!'} ${gray('(')}${took.join(gray(', '))}${gray(')')}`,
358
+ )
130
359
  spinner.stop()
131
360
  process.exit(0)
package/eslint.config.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { includeIgnoreFile } from '@eslint/compat'
2
2
  import js from '@eslint/js'
3
+ import pnpmCatalogs from 'eslint-plugin-pnpm-catalogs'
3
4
  import svelte from 'eslint-plugin-svelte'
4
5
  import unusedImports from 'eslint-plugin-unused-imports'
5
6
  import globals from 'globals'
@@ -66,6 +67,16 @@ export const config = [
66
67
  'no-empty': ['error', { allowEmptyCatch: true }],
67
68
  },
68
69
  },
70
+ {
71
+ name: '@kitql:pnpmCatalogs',
72
+ plugins: {
73
+ pnpmCatalogs,
74
+ },
75
+ rules: {
76
+ 'pnpmCatalogs/enforce-catalog': 'error',
77
+ 'pnpmCatalogs/valid-catalog': 'error',
78
+ },
79
+ },
69
80
  {
70
81
  name: '@kitql:rules',
71
82
  rules: {
@@ -86,8 +97,11 @@ export const config = [
86
97
 
87
98
  'no-undef': 'off',
88
99
  'no-inner-declarations': 'off',
100
+
89
101
  'svelte/no-at-html-tags': 'off',
90
102
  'svelte/no-inner-declarations': 'off',
103
+
104
+ 'svelte/require-each-key': 'warn',
91
105
  },
92
106
  },
93
107
  ]
@@ -1,5 +1,5 @@
1
- import { statSync } from 'fs'
2
- import { resolve } from 'path'
1
+ import { statSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
3
 
4
4
  export const findFileOrUp = (fileName, options) => {
5
5
  // Find file recursively 4 levels max up
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitql/eslint-config",
3
- "version": "0.5.3",
3
+ "version": "0.5.5-next.0",
4
4
  "type": "module",
5
5
  "funding": "https://github.com/sponsors/jycouet",
6
6
  "homepage": "https://www.kitql.dev/",
@@ -30,18 +30,19 @@
30
30
  "eslint-config"
31
31
  ],
32
32
  "dependencies": {
33
- "@eslint/compat": "^1.1.1",
34
- "@eslint/js": "^9.10.0",
33
+ "@eslint/compat": "1.1.1",
34
+ "@eslint/js": "9.10.0",
35
35
  "@theguild/prettier-config": "3.0.0",
36
36
  "@types/eslint": "9.6.1",
37
- "@typescript-eslint/parser": "^8.5.0",
38
- "commander": "13.0.0",
39
- "eslint": "^9.10.0",
40
- "eslint-plugin-svelte": "2.46.0",
41
- "eslint-plugin-unused-imports": "^4.1.4",
37
+ "@typescript-eslint/parser": "8.5.0",
38
+ "commander": "13.1.0",
39
+ "eslint": "9.10.0",
40
+ "eslint-plugin-pnpm-catalogs": "0.0.2",
41
+ "eslint-plugin-svelte": "3.0.3",
42
+ "eslint-plugin-unused-imports": "4.1.4",
42
43
  "globals": "16.0.0",
43
- "ora": "^8.1.0",
44
- "prettier": "3.4.2",
44
+ "ora": "8.1.0",
45
+ "prettier": "3.5.3",
45
46
  "prettier-plugin-svelte": "3.3.2",
46
47
  "prettier-plugin-tailwindcss": "0.6.6",
47
48
  "typescript-eslint": "8.26.0",
@@ -52,7 +53,7 @@
52
53
  },
53
54
  "sideEffects": false,
54
55
  "scripts": {
55
- "format": "node ./cmd.js -f",
56
+ "format": "node ./cmd.js -f -d --verbose",
56
57
  "format:example": "kitql-lint --format",
57
58
  "lint": "node ./cmd.js --verbose -p none",
58
59
  "lint:example": "kitql-lint",