@kitql/eslint-config 0.5.4 → 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.
Files changed (2) hide show
  1. package/cmd.js +226 -12
  2. package/package.json +5 -5
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 `),
@@ -19,6 +22,12 @@ program.addOption(new Option('-g, --glob <type>', 'file/dir/glob (. by default)'
19
22
  program.addOption(new Option('--eslint-only', 'only run eslint', false))
20
23
  program.addOption(new Option('--prettier-only', 'only run prettier', false))
21
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
+ )
22
31
  program.addOption(
23
32
  new Option(
24
33
  '-p, --prefix <type>',
@@ -34,11 +43,13 @@ const pathPrettierIgnore = findFileOrUp('.prettierignore')
34
43
  const pathPrettierMjs = findFileOrUp('.prettierrc.mjs')
35
44
 
36
45
  const format = options_cli.format ?? false
37
- const glob = options_cli.glob ?? '.'
46
+ let glob = options_cli.glob ?? '.'
38
47
  const verbose = options_cli.verbose ?? false
39
48
  const pre = options_cli.prefix ?? 'none'
40
49
  const eslintOnly = options_cli.eslintOnly ?? false
41
50
  const prettierOnly = options_cli.prettierOnly ?? false
51
+ const diffOnly = options_cli.diffOnly ?? false
52
+ const baseBranch = options_cli.baseBranch ?? 'main'
42
53
 
43
54
  let preToUse = ''
44
55
  if (pre === 'npm') {
@@ -78,10 +89,191 @@ async function customSpawn(cmd) {
78
89
  return data
79
90
  }
80
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
+
81
273
  async function eslintRun() {
82
274
  const cmdEsLint =
83
275
  preToUse +
84
- `eslint` +
276
+ `eslint --no-warn-ignored` +
85
277
  // format or not
86
278
  `${format ? ' --fix' : ''}` +
87
279
  // exec
@@ -116,11 +308,31 @@ async function prettierRun() {
116
308
  }
117
309
 
118
310
  const took = []
119
- if (!prettierOnly) {
120
- const esLintStart = Date.now()
311
+
312
+ const display = (text, time) => {
313
+ return `${gray(text)} ${green((time / 1000).toFixed(3))}${gray('s')}`
314
+ }
315
+
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()
121
333
  const eslintCode = await eslintRun()
122
- const esLintTook = Date.now() - esLintStart
123
- took.push(`eslint: ${esLintTook}ms`)
334
+ const esLintTook = performance.now() - esLintStart
335
+ took.push(display('eslint', esLintTook))
124
336
  if (eslintCode.status) {
125
337
  spinner.prefixText = bgRedBright(` kitql-lint `)
126
338
  spinner.fail(red(`eslint failed, check logs above.`))
@@ -128,11 +340,11 @@ if (!prettierOnly) {
128
340
  }
129
341
  }
130
342
 
131
- if (!eslintOnly) {
132
- const prettierStart = Date.now()
343
+ if (!eslintOnly && glob) {
344
+ const prettierStart = performance.now()
133
345
  const prettierCode = await prettierRun()
134
- const prettierTook = Date.now() - prettierStart
135
- took.push(`prettier: ${prettierTook}ms`)
346
+ const prettierTook = performance.now() - prettierStart
347
+ took.push(display('prettier', prettierTook))
136
348
  if (prettierCode.status) {
137
349
  spinner.prefixText = bgRedBright(` kitql-lint `)
138
350
  spinner.fail(red(`prettier failed, check logs above.`))
@@ -141,6 +353,8 @@ if (!eslintOnly) {
141
353
  }
142
354
 
143
355
  spinner.prefixText = bgGreen(` kitql-lint `)
144
- spinner.succeed(`All good, your files looks great! ${gray(`(${took.join(', ')})`)}`)
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
+ )
145
359
  spinner.stop()
146
360
  process.exit(0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitql/eslint-config",
3
- "version": "0.5.4",
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,13 +30,13 @@
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
37
  "@typescript-eslint/parser": "8.5.0",
38
38
  "commander": "13.1.0",
39
- "eslint": "^9.10.0",
39
+ "eslint": "9.10.0",
40
40
  "eslint-plugin-pnpm-catalogs": "0.0.2",
41
41
  "eslint-plugin-svelte": "3.0.3",
42
42
  "eslint-plugin-unused-imports": "4.1.4",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "sideEffects": false,
55
55
  "scripts": {
56
- "format": "node ./cmd.js -f",
56
+ "format": "node ./cmd.js -f -d --verbose",
57
57
  "format:example": "kitql-lint --format",
58
58
  "lint": "node ./cmd.js --verbose -p none",
59
59
  "lint:example": "kitql-lint",