oxlint-tui 1.0.14 → 1.0.16

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 (3) hide show
  1. package/README.md +6 -3
  2. package/package.json +1 -1
  3. package/tui.js +86 -47
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A lightweight, dependency-free Node.js Terminal User Interface (TUI) for browsing, toggling and visualizing [oxlint](https://github.com/oxc-project/oxc) rules and the number of warnings/errors they produce when toggled ON.
4
4
 
5
+ ![oxlint-tui-demo](https://github.com/user-attachments/assets/cefd05a6-8bda-4622-8adf-008402f856b4)
6
+
5
7
  It automatically loads your local configuration to show you the status of the rules toggled in your project and allows you to toggle them by selecting a rule in the Rules pane and pressing <kbd>1</kbd>, <kbd>2</kbd>, or <kbd>3</kbd>. The config file is modified in real-time.
6
8
 
7
9
  Pressing <kbd>r</kbd> will lint the project using "oxlint". Pressing <kbd>t</kbd> will lint include the oxlint-tsgolint package and lint the project using "oxlint --type-aware". The results are presented directly in the interface.
@@ -60,8 +62,9 @@ oxlint-tui
60
62
  | **1** | Set selected rule to "off" |
61
63
  | **2** | Set selected rule to "warn" |
62
64
  | **3** | Set selected rule to "error" |
63
- | **r** | Run the linter with "oxlint" |
64
- | **t** | Run the linter with "-p oxlint-tsgolint@latest oxlint --type-aware" |
65
+ | **x** | Run the selected rule |
66
+ | **r** | Run all enabled rules with "oxlint" |
67
+ | **t** | Run all enabled rules with "-p oxlint-tsgolint@latest oxlint --type-aware" |
65
68
  | **Enter** | Open Rule Documentation in Browser |
66
69
  | **q** / **Esc** | Quit |
67
70
 
@@ -78,4 +81,4 @@ If you're willing and able, please feel free to [contribute to this project](htt
78
81
 
79
82
  ## License
80
83
 
81
- MIT
84
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-tui",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "A Node TUI Oxlint rules and configuration browser",
5
5
  "type": "module",
6
6
  "bin": {
package/tui.js CHANGED
@@ -38,6 +38,7 @@ const KEY_MAP = {
38
38
  q: { type: "EXIT" },
39
39
  r: { type: "RUN_LINT" },
40
40
  t: { type: "RUN_TYPE_AWARE_LINT" },
41
+ x: { type: "RUN_SINGLE_RULE" },
41
42
  };
42
43
 
43
44
  let state = {
@@ -81,19 +82,40 @@ function updateConfig(rule, newStatus) {
81
82
  }
82
83
  }
83
84
 
84
- function runLint(type_aware) {
85
+ function runLint({ typeAware = false, rule = null } = {}) {
85
86
  if (state.isLinting) return;
86
87
 
87
88
  state.isLinting = true;
88
- state.message = type_aware ? "Linting with --type-aware)..." : "Linting...";
89
+
90
+ let ruleName = rule ? `${rule.scope}/${rule.value}` : null;
91
+ typeAware = typeAware || rule?.type_aware;
92
+
93
+ state.message = "Linting";
94
+ if (ruleName) state.message += ` [${ruleName}]`;
95
+ if (typeAware) state.message += " with --type-aware";
96
+ state.message += "...";
97
+
89
98
  state.messageType = "info";
99
+
90
100
  render();
91
101
 
92
102
  const npxCmd = platform === "win32" ? "npx.cmd" : "npx";
93
103
 
94
- const args = type_aware
95
- ? ["-q", "--yes", "--package", `oxlint@${OXLINT_VERSION}`, "--package", `oxlint-tsgolint@${TSGOLINT_VERSION}`, "--", "oxlint", "--type-aware",]
96
- : ["-q", "--yes", "--package", `oxlint@${OXLINT_VERSION}`, "--", "oxlint"];
104
+ const args = ["-q", "--yes", "--package", `oxlint@${OXLINT_VERSION}`];
105
+
106
+ if (typeAware) {
107
+ args.push("--package", `oxlint-tsgolint@${TSGOLINT_VERSION}`);
108
+ }
109
+
110
+ args.push("--", "oxlint");
111
+
112
+ if (typeAware) {
113
+ args.push("--type-aware");
114
+ }
115
+
116
+ if (ruleName) {
117
+ args.push("-A", "all", "-D", ruleName);
118
+ }
97
119
 
98
120
  const child = spawn(npxCmd, args);
99
121
 
@@ -116,14 +138,17 @@ function runLint(type_aware) {
116
138
  );
117
139
 
118
140
  if (summaryMatch) {
119
- state.message = summaryMatch[0];
120
141
  const errors = parseInt(summaryMatch[2]);
142
+ state.message = ruleName
143
+ ? `[${ruleName}] Found ${errors} issue${errors === 1 ? "" : "s"}`
144
+ : (state.message = summaryMatch[0]);
121
145
  state.messageType = errors > 0 ? "error" : "warn";
122
146
  } else if (
123
147
  stdoutData.toLowerCase().includes("finished") ||
124
148
  (code === 0 && stdoutData.length < 200)
125
149
  ) {
126
150
  state.message = "Linting passed! 0 issues found.";
151
+ if (ruleName) state.message = `[${ruleName}] ${state.message}`;
127
152
  state.messageType = "success";
128
153
  } else {
129
154
  const cleanError = stderrData
@@ -145,7 +170,9 @@ function runLint(type_aware) {
145
170
  });
146
171
  }
147
172
 
148
- function reducer(state, action) {
173
+ function execute(action) {
174
+ if (!action) return;
175
+
149
176
  const {
150
177
  categories,
151
178
  rulesByCategory,
@@ -160,10 +187,33 @@ function reducer(state, action) {
160
187
  const catViewHeight = viewHeight - statsHeight;
161
188
 
162
189
  switch (action.type) {
190
+ case "EXIT":
191
+ exitAltScreen();
192
+ exit(0);
193
+ return;
194
+
195
+ case "RUN_LINT":
196
+ runLint();
197
+ return;
198
+
199
+ case "RUN_SINGLE_RULE": {
200
+ const rule = currentRules[selectedRuleIdx];
201
+ if (rule) runLint({ rule });
202
+ return;
203
+ }
204
+
205
+ case "OPEN_DOCS": {
206
+ if (activePane === 1) {
207
+ const rule = currentRules[selectedRuleIdx];
208
+ if (rule) openUrl(rule.docs_url || rule.url);
209
+ }
210
+ return;
211
+ }
212
+
163
213
  case "SET_STATUS": {
164
- if (activePane !== 1) return state;
214
+ if (activePane !== 1) return;
165
215
  const rule = currentRules[selectedRuleIdx];
166
- if (!rule) return state;
216
+ if (!rule) return;
167
217
  updateConfig(rule, action.value);
168
218
  const updatedRules = [...currentRules];
169
219
  updatedRules[selectedRuleIdx] = {
@@ -171,7 +221,7 @@ function reducer(state, action) {
171
221
  configStatus: action.value,
172
222
  isActive: action.value === "error" || action.value === "warn",
173
223
  };
174
- return {
224
+ state = {
175
225
  ...state,
176
226
  message: `Rule '${rule.value}' set to: ${action.value}`,
177
227
  messageType: "info",
@@ -180,19 +230,29 @@ function reducer(state, action) {
180
230
  [currentCat]: updatedRules,
181
231
  },
182
232
  };
233
+ render();
234
+ return;
183
235
  }
236
+
184
237
  case "MOVE_RIGHT":
185
- if (activePane !== 1) return { ...state, activePane: activePane + 1 };
186
- return state;
238
+ if (activePane !== 1) {
239
+ state = { ...state, activePane: activePane + 1 };
240
+ render();
241
+ }
242
+ return;
187
243
 
188
244
  case "MOVE_LEFT":
189
- if (activePane !== 0) return { ...state, activePane: activePane - 1 };
190
- return state;
245
+ if (activePane !== 0) {
246
+ state = { ...state, activePane: activePane - 1 };
247
+ render();
248
+ }
249
+ return;
250
+
191
251
  case "MOVE_UP":
192
252
  if (activePane === 0) {
193
253
  const nextIdx =
194
254
  selectedCatIdx === 0 ? categories.length - 1 : selectedCatIdx - 1;
195
- return {
255
+ state = {
196
256
  ...state,
197
257
  selectedCatIdx: nextIdx,
198
258
  selectedRuleIdx: 0,
@@ -202,18 +262,20 @@ function reducer(state, action) {
202
262
  } else if (activePane === 1) {
203
263
  const nextIdx =
204
264
  selectedRuleIdx === 0 ? currentRules.length - 1 : selectedRuleIdx - 1;
205
- return {
265
+ state = {
206
266
  ...state,
207
267
  selectedRuleIdx: nextIdx,
208
268
  scrollRule: updateScroll(nextIdx, state.scrollRule, viewHeight),
209
269
  };
210
270
  }
211
- return state;
271
+ render();
272
+ return;
273
+
212
274
  case "MOVE_DOWN":
213
275
  if (activePane === 0) {
214
276
  const nextIdx =
215
277
  selectedCatIdx === categories.length - 1 ? 0 : selectedCatIdx + 1;
216
- return {
278
+ state = {
217
279
  ...state,
218
280
  selectedCatIdx: nextIdx,
219
281
  selectedRuleIdx: 0,
@@ -223,15 +285,14 @@ function reducer(state, action) {
223
285
  } else if (activePane === 1) {
224
286
  const nextIdx =
225
287
  selectedRuleIdx === currentRules.length - 1 ? 0 : selectedRuleIdx + 1;
226
- return {
288
+ state = {
227
289
  ...state,
228
290
  selectedRuleIdx: nextIdx,
229
291
  scrollRule: updateScroll(nextIdx, state.scrollRule, viewHeight),
230
292
  };
231
293
  }
232
- return state;
233
- default:
234
- return state;
294
+ render();
295
+ return;
235
296
  }
236
297
  }
237
298
 
@@ -292,7 +353,7 @@ function loadRules() {
292
353
  config = JSON.parse(
293
354
  stripJsonComments(fs.readFileSync(configPath, "utf8")),
294
355
  );
295
- } catch (e) { }
356
+ } catch (e) {}
296
357
  }
297
358
 
298
359
  const map = {};
@@ -553,7 +614,7 @@ function render() {
553
614
  ? `Config: ${state.configPath}`
554
615
  : "No config loaded";
555
616
  buffer.push(
556
- `\x1b[${rows - 1};2H${COLORS.dim}Arrows/HJKL: Nav | 1-3: Status | R: Lint | T: Lint with --type-aware | Enter: Docs | Q: Quit | ${footerConfig}${COLORS.reset}`,
617
+ `\x1b[${rows - 1};2H${COLORS.dim}Arrows/HJKL: Nav | 1-3: Status | R: Lint | T: Lint with --type-aware | X: Run rule | Enter: Docs | Q: Quit | ${footerConfig}${COLORS.reset}`,
557
618
  );
558
619
  write(buffer.join(""));
559
620
  }
@@ -567,29 +628,7 @@ stdin.on("keypress", (_, key) => {
567
628
  (key.ctrl && key.name === "c"
568
629
  ? { type: "EXIT" }
569
630
  : KEY_MAP[key.sequence] || null);
570
- if (!action) return;
571
-
572
- if (action.type === "EXIT") {
573
- exitAltScreen();
574
- exit(0);
575
- }
576
- if (action.type === "RUN_LINT") {
577
- runLint(false);
578
- return;
579
- }
580
- if (action.type === "RUN_TYPE_AWARE_LINT") {
581
- runLint(true);
582
- return;
583
- }
584
- if (action.type === "OPEN_DOCS" && state.activePane === 1) {
585
- const currentCat = state.categories[state.selectedCatIdx];
586
- const rule = state.rulesByCategory[currentCat]?.[state.selectedRuleIdx];
587
- if (rule) openUrl(rule.docs_url || rule.url);
588
- return;
589
- }
590
-
591
- state = reducer(state, action);
592
- render();
631
+ execute(action);
593
632
  });
594
633
 
595
634
  stdout.on("resize", render);