@vinsonchuong/xo_d 1.1.2 → 1.2.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 (4) hide show
  1. package/cli.test.mjs +4 -2
  2. package/client.mjs +144 -134
  3. package/package.json +15 -14
  4. package/server.mjs +40 -33
package/cli.test.mjs CHANGED
@@ -12,16 +12,18 @@ async function cli(args, {cwd, stdin}) {
12
12
  child.stdin.end(stdin)
13
13
  }
14
14
 
15
- const [exitCode, stdout] = await Promise.all([
15
+ const [exitCode, stdout, stderr] = await Promise.all([
16
16
  new Promise((resolve) => {
17
17
  child.on('exit', resolve)
18
18
  }),
19
19
  getStream(child.stdout),
20
+ getStream(child.stderr),
20
21
  ])
21
22
 
22
23
  return {
23
24
  exitCode,
24
25
  stdout,
26
+ stderr,
25
27
  }
26
28
  }
27
29
 
@@ -45,7 +47,7 @@ test.serial('fix option', async (t) => {
45
47
 
46
48
  test.serial('fix option with stdin', async (t) => {
47
49
  const directory = await useTemporaryDirectory(t)
48
- const {stdout} = await cli(['--fix', '--stdin'], {
50
+ const {stdout} = await cli(['--fix', '--stdin', '--stdin-filename=fix.js'], {
49
51
  cwd: directory.path,
50
52
  stdin: 'console.log()',
51
53
  })
package/client.mjs CHANGED
@@ -1,12 +1,18 @@
1
1
  import process from 'node:process'
2
+ import fs from 'node:fs/promises'
2
3
  import {createRequire} from 'node:module'
4
+ import path from 'node:path'
3
5
  import getStdin from 'get-stdin'
6
+ import {pathExists} from 'path-exists'
7
+ import findCacheDirectory from 'find-cache-directory'
4
8
  import meow from 'meow'
5
- import semver from 'semver'
9
+
10
+ const cacheDirName = 'xo-linter'
11
+ const tsExtensions = new Set(['ts', 'tsx', 'cts', 'mts'])
6
12
 
7
13
  const require = createRequire(import.meta.url)
8
14
 
9
- export default async function () {
15
+ export default async function run() {
10
16
  process.env.CORE_D_TITLE = 'xo_d'
11
17
  process.env.CORE_D_DOTFILE = '.xo_d'
12
18
  process.env.CORE_D_SERVICE = require.resolve('./server.js')
@@ -21,50 +27,32 @@ export default async function () {
21
27
  ) {
22
28
  coreD[xoDCommand]()
23
29
  } else {
24
- const {
25
- input,
26
- flags: options,
27
- showVersion,
28
- } = meow(
30
+ const cli = meow(
29
31
  `
30
- Usage
31
- $ xo_d [<file|glob> ...]
32
- $ xo_d [start|stop|status]
33
-
34
- Options
35
- --fix Automagically fix issues
36
- --reporter Reporter to use
37
- --env Environment preset [Can be set multiple times]
38
- --global Global variable [Can be set multiple times]
39
- --ignore Additional paths to ignore [Can be set multiple times]
40
- --space Use space indent instead of tabs [Default: 2]
41
- --no-semicolon Prevent use of semicolons
42
- --prettier Conform to Prettier code style
43
- --node-version Range of Node.js version to support
44
- --plugin Include third-party plugins [Can be set multiple times]
45
- --extend Extend defaults with a custom config [Can be set multiple times]
46
- --open Open files with issues in your editor
47
- --quiet Show only errors and no warnings
48
- --extension Additional extension to lint [Can be set multiple times]
49
- --cwd=<dir> Working directory for files
50
- --stdin Validate/fix code from stdin
51
- --stdin-filename Specify a filename for the --stdin option
52
- --print-config Print the effective ESLint config for the given file
53
-
54
- Examples
55
- $ xo
56
- $ xo index.js
57
- $ xo *.js !foo.js
58
- $ xo --space
59
- $ xo --env=node --env=mocha
60
- $ xo --plugin=react
61
- $ xo --plugin=html --extension=html
62
- $ echo 'const x=true' | xo --stdin --fix
63
- $ xo --print-config=index.js
64
-
65
- Tips
66
- - Add XO to your project with \`npm init xo\`.
67
- - Put options in package.json instead of using flags so other tools can read it.
32
+ Usage
33
+ $ xo_d [<file|glob> ...]
34
+ $ xo_d [start|stop|status]
35
+
36
+ Options
37
+ --fix Automagically fix issues
38
+ --reporter Reporter to use
39
+ --space Use space indent instead of tabs [Default: 2]
40
+ --config Path to a XO configuration file
41
+ --semicolon Use semicolons [Default: true]
42
+ --react Include React specific parsing and xo-react linting rules [Default: false]
43
+ --prettier Format with prettier or turn off prettier conflicted rules when set to 'compat' [Default: false]
44
+ --version Print XO version
45
+ --quiet Show only errors and no warnings
46
+ --stdin Validate/fix code from stdin
47
+ --stdin-filename Specify a filename for the --stdin option
48
+ --ignore Ignore pattern globs, can be set multiple times
49
+ --cwd=<dir> Working directory for files [Default: process.cwd()]
50
+
51
+ Examples
52
+ $ xo
53
+ $ xo index.js
54
+ $ xo *.js !foo.js
55
+ $ xo --space
68
56
  `,
69
57
  {
70
58
  importMeta: import.meta,
@@ -73,24 +61,19 @@ export default async function () {
73
61
  flags: {
74
62
  fix: {
75
63
  type: 'boolean',
64
+ default: false,
76
65
  },
77
66
  reporter: {
78
67
  type: 'string',
79
68
  },
80
- env: {
81
- type: 'string',
82
- isMultiple: true,
83
- },
84
- global: {
69
+ space: {
85
70
  type: 'string',
86
- isMultiple: true,
87
71
  },
88
- ignore: {
72
+ config: {
89
73
  type: 'string',
90
- isMultiple: true,
91
74
  },
92
- space: {
93
- type: 'string',
75
+ quiet: {
76
+ type: 'boolean',
94
77
  },
95
78
  semicolon: {
96
79
  type: 'boolean',
@@ -98,124 +81,151 @@ export default async function () {
98
81
  prettier: {
99
82
  type: 'boolean',
100
83
  },
101
- nodeVersion: {
102
- type: 'string',
103
- },
104
- plugin: {
105
- type: 'string',
106
- isMultiple: true,
84
+ react: {
85
+ type: 'boolean',
86
+ default: false,
107
87
  },
108
- extend: {
88
+ cwd: {
109
89
  type: 'string',
110
- isMultiple: true,
90
+ default: process.cwd(),
111
91
  },
112
- open: {
92
+ version: {
113
93
  type: 'boolean',
114
94
  },
115
- quiet: {
95
+ stdin: {
116
96
  type: 'boolean',
117
97
  },
118
- extension: {
119
- type: 'string',
120
- isMultiple: true,
121
- },
122
- cwd: {
123
- type: 'string',
124
- },
125
- printConfig: {
98
+ stdinFilename: {
126
99
  type: 'string',
100
+ default: 'stdin.js',
127
101
  },
128
- stdin: {
102
+ open: {
129
103
  type: 'boolean',
130
104
  },
131
- stdinFilename: {
105
+ ignore: {
132
106
  type: 'string',
107
+ isMultiple: true,
108
+ aliases: ['ignores'],
133
109
  },
134
110
  },
135
111
  },
136
112
  )
137
113
 
138
- for (const key in options) {
139
- if (Array.isArray(options[key]) && options[key].length === 0) {
140
- delete options[key]
141
- }
114
+ const {input, flags: cliOptions, showVersion} = cli
115
+
116
+ const baseXoConfigOptions = {
117
+ space: cliOptions.space,
118
+ semicolon: cliOptions.semicolon,
119
+ prettier: cliOptions.prettier,
120
+ ignores: cliOptions.ignore,
121
+ react: cliOptions.react,
122
+ }
123
+
124
+ const linterOptions = {
125
+ fix: cliOptions.fix,
126
+ cwd: (cliOptions.cwd && path.resolve(cliOptions.cwd)) ?? process.cwd(),
127
+ quiet: cliOptions.quiet,
128
+ ts: true,
142
129
  }
143
130
 
144
- if (typeof options.space === 'string') {
145
- if (/^\d+$/u.test(options.space)) {
146
- options.space = Number.parseInt(options.space, 10)
147
- } else if (options.space === 'true') {
148
- options.space = true
149
- } else if (options.space === 'false') {
150
- options.space = false
131
+ // Make data types for `options.space` match those of the API
132
+ if (typeof cliOptions.space === 'string') {
133
+ cliOptions.space = cliOptions.space.trim()
134
+
135
+ if (/^\d+$/u.test(cliOptions.space)) {
136
+ baseXoConfigOptions.space = Number.parseInt(cliOptions.space, 10)
137
+ } else if (cliOptions.space === 'true') {
138
+ baseXoConfigOptions.space = true
139
+ } else if (cliOptions.space === 'false') {
140
+ baseXoConfigOptions.space = false
151
141
  } else {
152
- if (options.space !== '') {
153
- input.push(options.space)
142
+ if (cliOptions.space !== '') {
143
+ // Assume `options.space` was set to a filename when run as `xo --space file.js`
144
+ input.push(cliOptions.space)
154
145
  }
155
146
 
156
- options.space = true
147
+ baseXoConfigOptions.space = true
157
148
  }
158
149
  }
159
150
 
160
- if (process.env.GITHUB_ACTIONS && !options.fix && !options.reporter) {
161
- options.quiet = true
162
- }
163
-
164
- if (input[0] === '-') {
165
- options.stdin = true
166
- input.shift()
151
+ if (
152
+ process.env.GITHUB_ACTIONS &&
153
+ !linterOptions.fix &&
154
+ !cliOptions.reporter
155
+ ) {
156
+ linterOptions.quiet = true
167
157
  }
168
158
 
169
- if (options.version) {
159
+ if (cliOptions.version) {
170
160
  showVersion()
171
161
  }
172
162
 
173
- if (options.nodeVersion) {
174
- if (options.nodeVersion === 'false') {
175
- options.nodeVersion = false
176
- } else if (!semver.validRange(options.nodeVersion)) {
177
- console.error(
178
- 'The `--node-engine` flag must be a valid semver range (for example `>=6`)',
179
- )
180
- process.exit(1)
181
- }
182
- }
163
+ if (cliOptions.stdin) {
164
+ const stdin = await getStdin()
183
165
 
184
- if (typeof options.printConfig === 'string') {
185
- if (input.length > 0 || options.printConfig === '') {
186
- console.error(
187
- 'The `--print-config` flag must be used with exactly one filename',
166
+ let shouldRemoveStdInFile = false
167
+
168
+ // For TypeScript, we need a file on the filesystem to lint it or else @typescript-eslint will blow up.
169
+ // We create a temporary file in the node_modules/.cache/xo-linter directory to avoid conflicts with the user's files and lint that file as if it were the stdin input as a work around.
170
+ // We clean up the file after linting.
171
+ if (
172
+ cliOptions.stdinFilename &&
173
+ tsExtensions.has(path.extname(cliOptions.stdinFilename).slice(1))
174
+ ) {
175
+ const absoluteFilePath = path.resolve(
176
+ cliOptions.cwd,
177
+ cliOptions.stdinFilename,
188
178
  )
189
- process.exit(1)
190
- }
191
-
192
- if (options.stdin) {
193
- console.error('The `--print-config` flag is not supported on stdin')
194
- process.exit(1)
179
+ if (!(await pathExists(absoluteFilePath))) {
180
+ const cacheDir =
181
+ findCacheDirectory({name: cacheDirName, cwd: linterOptions.cwd}) ??
182
+ path.join(cliOptions.cwd, 'node_modules', '.cache', cacheDirName)
183
+ cliOptions.stdinFilename = path.join(
184
+ cacheDir,
185
+ path.basename(absoluteFilePath),
186
+ )
187
+ shouldRemoveStdInFile = true
188
+ baseXoConfigOptions.ignores = [
189
+ '!**/node_modules/**',
190
+ '!node_modules/**',
191
+ '!node_modules/',
192
+ `!${path.relative(cliOptions.cwd, cliOptions.stdinFilename)}`,
193
+ ]
194
+ if (!(await pathExists(path.dirname(cliOptions.stdinFilename)))) {
195
+ await fs.mkdir(path.dirname(cliOptions.stdinFilename), {
196
+ recursive: true,
197
+ })
198
+ }
199
+
200
+ await fs.writeFile(cliOptions.stdinFilename, stdin)
201
+ }
195
202
  }
196
203
 
197
- options.filePath = options.printConfig
198
-
199
- coreD.invoke([input, options, null])
200
- } else if (options.stdin) {
201
- const stdin = await getStdin()
202
-
203
- if (options.stdinFilename) {
204
- options.filePath = options.stdinFilename
204
+ if (cliOptions.fix) {
205
+ return coreD.invoke([
206
+ cliOptions,
207
+ linterOptions,
208
+ baseXoConfigOptions,
209
+ input,
210
+ stdin,
211
+ ])
205
212
  }
206
213
 
207
- if (options.fix) {
208
- return coreD.invoke([input, options, stdin])
209
- }
214
+ coreD.invoke([
215
+ cliOptions,
216
+ linterOptions,
217
+ baseXoConfigOptions,
218
+ input,
219
+ null,
220
+ ])
210
221
 
211
- if (options.open) {
212
- console.error('The `--open` flag is not supported on stdin')
213
- process.exit(1)
222
+ if (shouldRemoveStdInFile) {
223
+ await fs.rm(cliOptions.stdinFilename)
214
224
  }
215
225
 
216
- coreD.invoke([input, options, stdin])
217
- } else {
218
- coreD.invoke([input, options, null])
226
+ return
219
227
  }
228
+
229
+ coreD.invoke([cliOptions, linterOptions, baseXoConfigOptions, input, null])
220
230
  }
221
231
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vinsonchuong/xo_d",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Speed up xo in the same way as eslint_d",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/vinsonchuong/xo_d",
@@ -13,22 +13,22 @@
13
13
  "release": "semantic-release"
14
14
  },
15
15
  "type": "commonjs",
16
- "bin": {
17
- "xo_d": "cli.js"
18
- },
16
+ "bin": "cli.js",
19
17
  "dependencies": {
20
- "core_d": "^5.0.1",
21
- "eslint-formatter-pretty": "^4.1.0",
18
+ "core_d": "^6.1.0",
19
+ "eslint-formatter-pretty": "^6.0.1",
20
+ "find-cache-directory": "^6.0.0",
22
21
  "get-stdin": "^9.0.0",
23
- "meow": "^11.0.0",
24
- "semver": "^7.3.8",
25
- "xo": "^0.53.0"
22
+ "meow": "^13.2.0",
23
+ "path-exists": "^5.0.0",
24
+ "xo": "^1.1.0"
26
25
  },
27
26
  "devDependencies": {
28
- "ava": "^5.0.1",
29
- "ava-patterns": "^3.2.0",
30
- "get-stream": "^6.0.1",
31
- "semantic-release": "^20.0.0"
27
+ "ava": "^5.3.1",
28
+ "ava-patterns": "^3.2.1",
29
+ "get-stream": "^9.0.1",
30
+ "semantic-release": "^24.2.5",
31
+ "typescript": "^5.8.3"
32
32
  },
33
33
  "ava": {
34
34
  "verbose": true
@@ -52,5 +52,6 @@
52
52
  },
53
53
  "publishConfig": {
54
54
  "access": "public"
55
- }
55
+ },
56
+ "packageManager": "yarn@4.9.2"
56
57
  }
package/server.mjs CHANGED
@@ -1,46 +1,53 @@
1
- import process from 'node:process'
2
- import xo from 'xo'
1
+ import {Xo} from 'xo'
3
2
  import formatterPretty from 'eslint-formatter-pretty'
4
- import openReport from 'xo/lib/open-report.js'
5
3
 
6
- async function log(report, options) {
7
- const reporter =
8
- options.reporter || process.env.GITHUB_ACTIONS
9
- ? await xo.getFormatter(options.reporter || 'compact')
10
- : formatterPretty
11
-
12
- const text = reporter(report.results, {rulesMeta: report.rulesMeta})
13
- const exitCode = report.errorCount === 0 ? 0 : 1
14
- return `${text}\n# exit ${exitCode}`
15
- }
16
-
17
- export default async function ([input, options, stdin]) {
18
- if (typeof options.printConfig === 'string') {
19
- options.filePath = options.printConfig
20
- const config = await xo.getConfig(options)
21
- return JSON.stringify(config, undefined, '\t')
4
+ export default async function run([
5
+ cliOptions,
6
+ linterOptions,
7
+ baseXoConfigOptions,
8
+ input,
9
+ stdin,
10
+ ]) {
11
+ const xo = new Xo(linterOptions, baseXoConfigOptions)
12
+
13
+ const log = async (report) => {
14
+ const reporter = cliOptions.reporter
15
+ ? await new Xo(linterOptions, baseXoConfigOptions).getFormatter(
16
+ cliOptions.reporter,
17
+ )
18
+ : {format: formatterPretty}
19
+
20
+ const text = reporter.format(report.results, {
21
+ cwd: linterOptions.cwd,
22
+ ...report,
23
+ })
24
+ const exitCode = report.errorCount === 0 ? 0 : 1
25
+
26
+ return `${text}\n# exit ${exitCode}`
22
27
  }
23
28
 
24
- if (options.stdin) {
25
- if (options.fix) {
29
+ if (cliOptions.stdin) {
30
+ if (cliOptions.fix) {
26
31
  const {
27
32
  results: [result],
28
- } = await xo.lintText(stdin, options)
29
- return (result && result.output) || stdin
33
+ } = await xo.lintText(stdin, {
34
+ filePath: cliOptions.stdinFilename,
35
+ })
36
+ return result?.output ?? stdin
30
37
  }
31
38
 
32
- return log(await xo.lintText(stdin, options), options)
33
- }
34
-
35
- const report = await xo.lintFiles(input, options)
36
-
37
- if (options.fix) {
38
- await xo.outputFixes(report)
39
+ return log(
40
+ await xo.lintText(stdin, {
41
+ filePath: cliOptions.stdinFilename,
42
+ warnIgnored: false,
43
+ }),
44
+ )
39
45
  }
40
46
 
41
- if (options.open) {
42
- openReport(report)
47
+ const report = await xo.lintFiles(input)
48
+ if (cliOptions.fix) {
49
+ await Xo.outputFixes(report)
43
50
  }
44
51
 
45
- return log(report, options)
52
+ return log(report)
46
53
  }