tz-clean 2.3.2 ā 2.4.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/.dependency-cruiser.cjs +93 -94
- package/.github/workflows/ci.yml +42 -42
- package/.prettierrc +9 -9
- package/README-DEV.md +123 -123
- package/README-USER.md +105 -105
- package/README.md +16 -16
- package/__tests__/cli.test.js +8 -8
- package/commitlint.config.js +1 -1
- package/cspell.json +33 -33
- package/eslint.config.mjs +72 -66
- package/index.js +386 -428
- package/knip.json +5 -5
- package/package.json +67 -67
- package/report-template.html +357 -357
- package/tsconfig.json +16 -16
package/index.js
CHANGED
|
@@ -1,428 +1,386 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import http from 'http';
|
|
5
|
-
import open from 'open';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import prompts from 'prompts';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
|
|
10
|
-
const filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const dirname = path.dirname(filename);
|
|
12
|
-
const configDir = dirname;
|
|
13
|
-
const targetDir = process.cwd();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
function generateHtmlReport(reportData, fixedFiles) {
|
|
37
|
-
const templatePath = path.join(configDir, 'report-template.html');
|
|
38
|
-
let html = fs.readFileSync(templatePath, 'utf-8');
|
|
39
|
-
|
|
40
|
-
html = html.replace('/*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
console.log('\n
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
console.log(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
{
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
return fixedFiles;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Executes TypeScript type checking.
|
|
392
|
-
* @returns {{error: string|null, ran: boolean}} The result of type checking.
|
|
393
|
-
*/
|
|
394
|
-
function runTypecheckTools() {
|
|
395
|
-
let typecheckError = null;
|
|
396
|
-
let ran = false;
|
|
397
|
-
if (fs.existsSync(path.join(targetDir, 'tsconfig.json'))) {
|
|
398
|
-
ran = true;
|
|
399
|
-
try {
|
|
400
|
-
execSync(`npx tsc --noEmit`, { stdio: 'ignore' });
|
|
401
|
-
} catch {
|
|
402
|
-
typecheckError = 'TypeScript validation failed. Check your types.';
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return { error: typecheckError, ran };
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Serves the HTML report on localhost.
|
|
410
|
-
* @param {string} html - The HTML string content.
|
|
411
|
-
* @returns {void}
|
|
412
|
-
*/
|
|
413
|
-
function serveHostedReport(html) {
|
|
414
|
-
const port = 8080;
|
|
415
|
-
const server = http.createServer((req, res) => {
|
|
416
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
417
|
-
res.end(html);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
server.listen(port, async () => {
|
|
421
|
-
const url = `http://localhost:${port}`;
|
|
422
|
-
console.log(`š Server running at ${url}`);
|
|
423
|
-
console.log('Press Ctrl+C to stop the server.');
|
|
424
|
-
await open(url);
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
run();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import http from 'http';
|
|
5
|
+
import open from 'open';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import prompts from 'prompts';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const dirname = path.dirname(filename);
|
|
12
|
+
const configDir = dirname;
|
|
13
|
+
const targetDir = process.cwd();
|
|
14
|
+
|
|
15
|
+
function buildIgnorePatterns(excludePaths) {
|
|
16
|
+
const defaults = [
|
|
17
|
+
new RegExp('node_modules'),
|
|
18
|
+
new RegExp('\\.min\\.(js|cjs|mjs)$'),
|
|
19
|
+
new RegExp('\\.bundle\\.js$'),
|
|
20
|
+
new RegExp('[/\\\\](dist|build|vendor|libs)[/\\\\]'),
|
|
21
|
+
];
|
|
22
|
+
const user = excludePaths.map((p) => new RegExp(p.replace(new RegExp('/', 'g'), '[/\\\\]')));
|
|
23
|
+
return [...defaults, ...user];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function calculateTotals(reportData) {
|
|
27
|
+
let totalErrors = 0;
|
|
28
|
+
let totalWarnings = 0;
|
|
29
|
+
reportData.forEach((file) => {
|
|
30
|
+
totalErrors += file.errorCount;
|
|
31
|
+
totalWarnings += file.warningCount;
|
|
32
|
+
});
|
|
33
|
+
return { totalErrors, totalWarnings };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function generateHtmlReport(reportData, fixedFiles) {
|
|
37
|
+
const templatePath = path.join(configDir, 'report-template.html');
|
|
38
|
+
let html = fs.readFileSync(templatePath, 'utf-8');
|
|
39
|
+
html = html.replace('/* REPORT_DATA_PLACEHOLDER */ null', JSON.stringify(reportData));
|
|
40
|
+
html = html.replace('/* FIXED_FILES_PLACEHOLDER */ []', JSON.stringify(fixedFiles));
|
|
41
|
+
const outputPath = path.join(targetDir, '.tz-clean-report.html');
|
|
42
|
+
fs.writeFileSync(outputPath, html);
|
|
43
|
+
return outputPath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseArgs() {
|
|
47
|
+
const args = process.argv.slice(2);
|
|
48
|
+
const runPrettier = !args.includes('--no-prettier');
|
|
49
|
+
const runEslint = !args.includes('--no-eslint');
|
|
50
|
+
const runTypecheck = !args.includes('--no-typecheck');
|
|
51
|
+
|
|
52
|
+
const excludePaths = [];
|
|
53
|
+
const includePaths = [];
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < args.length; i++) {
|
|
56
|
+
if (args[i] === '--include' && args[i + 1]) {
|
|
57
|
+
args[i + 1].split(',').forEach((p) => includePaths.push(p.trim()));
|
|
58
|
+
i++;
|
|
59
|
+
} else if (args[i] === '--exclude' && args[i + 1]) {
|
|
60
|
+
args[i + 1].split(',').forEach((p) => excludePaths.push(p.trim()));
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const tools = [];
|
|
66
|
+
if (runPrettier) tools.push('Prettier');
|
|
67
|
+
if (runEslint) tools.push('ESLint');
|
|
68
|
+
if (runTypecheck) tools.push('Typecheck');
|
|
69
|
+
|
|
70
|
+
return { excludePaths, includePaths, runEslint, runPrettier, runTypecheck, tools };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function printTerminalSummary(reportData, fixedFiles, totalErrors, totalWarnings, typecheckResult) {
|
|
74
|
+
console.log('\n--- š tz-clean Summary ---');
|
|
75
|
+
console.log(`Files Analyzed: ${reportData.length}`);
|
|
76
|
+
console.log(`Errors (Critical) : ${totalErrors > 0 ? 'ā ' + totalErrors : 'ā
0'}`);
|
|
77
|
+
console.log(`Warnings : ${totalWarnings > 0 ? 'ā ļø ' + totalWarnings : 'ā
0'}`);
|
|
78
|
+
|
|
79
|
+
if (!typecheckResult.ran) {
|
|
80
|
+
console.log(`Typecheck : ā ļø Skipped`);
|
|
81
|
+
} else if (typecheckResult.error) {
|
|
82
|
+
console.log(`Typecheck : ā ${typecheckResult.error}`);
|
|
83
|
+
} else {
|
|
84
|
+
console.log(`Typecheck : ā
Passed`);
|
|
85
|
+
}
|
|
86
|
+
console.log('---------------------------');
|
|
87
|
+
|
|
88
|
+
if (fixedFiles.length > 0) {
|
|
89
|
+
console.log('\n⨠Auto-Fixed (Formatted):');
|
|
90
|
+
fixedFiles.forEach((file) => console.log(` - ${file}`));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (totalErrors > 0 || totalWarnings > 0) {
|
|
94
|
+
console.log('\nš Details:');
|
|
95
|
+
reportData.forEach((file) => {
|
|
96
|
+
if (file.errorCount > 0 || file.warningCount > 0) {
|
|
97
|
+
console.log(`\nš ${path.relative(targetDir, file.filePath) || file.filePath}`);
|
|
98
|
+
file.messages.forEach((msg) => {
|
|
99
|
+
const severity = msg.severity === 2 ? 'ā Error' : 'ā ļø Warning';
|
|
100
|
+
console.log(` ${severity} [${msg.line || 0}:${msg.column || 0}] ${msg.message} (${msg.ruleId || ''})`);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
console.log('\n---------------------------\n');
|
|
105
|
+
} else {
|
|
106
|
+
console.log('\n');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function run() {
|
|
111
|
+
const { excludePaths, includePaths, runEslint, runPrettier, runTypecheck, tools } = parseArgs();
|
|
112
|
+
|
|
113
|
+
if (tools.length === 0) {
|
|
114
|
+
console.log('No tools selected to run.');
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(`š§¹ Running tz-clean (${tools.join(', ')})...`);
|
|
119
|
+
if (includePaths.length > 0) console.log(`š Include: ${includePaths.join(', ')}`);
|
|
120
|
+
if (excludePaths.length > 0) console.log(`š« Exclude: ${excludePaths.join(', ')}`);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
process.stdout.write('ā³ [1/4] Running Prettier...');
|
|
124
|
+
const fixedFiles = runPrettier ? runPrettierTools(includePaths, excludePaths) : [];
|
|
125
|
+
process.stdout.write(`\rā
[1/4] Prettier done (${fixedFiles.length} file(s) formatted) \n`);
|
|
126
|
+
|
|
127
|
+
process.stdout.write('ā³ [2/4] Stripping comments...');
|
|
128
|
+
const strippedFiles = stripCommentsAndEmptyLines(includePaths, excludePaths);
|
|
129
|
+
process.stdout.write(`\rā
[2/4] Comments stripped (${strippedFiles.length} file(s) modified) \n`);
|
|
130
|
+
|
|
131
|
+
const allFixedFiles = [...new Set([...fixedFiles, ...strippedFiles])];
|
|
132
|
+
|
|
133
|
+
process.stdout.write('ā³ [3/4] Running Typecheck...');
|
|
134
|
+
const typecheckResult = runTypecheck ? runTypecheckTools() : { error: null, ran: false };
|
|
135
|
+
let typecheckStatus = 'skipped';
|
|
136
|
+
if (typecheckResult.ran) {
|
|
137
|
+
typecheckStatus = typecheckResult.error ? 'failed' : 'passed';
|
|
138
|
+
}
|
|
139
|
+
process.stdout.write(`\rā
[3/4] Typecheck ${typecheckStatus} \n`);
|
|
140
|
+
|
|
141
|
+
process.stdout.write('ā³ [4/4] Running ESLint...');
|
|
142
|
+
const reportData = runEslint ? runEslintTools(includePaths, excludePaths) : [];
|
|
143
|
+
const { totalErrors, totalWarnings } = calculateTotals(reportData);
|
|
144
|
+
process.stdout.write(`\rā
[4/4] ESLint done (${totalErrors} error(s), ${totalWarnings} warning(s)) \n`);
|
|
145
|
+
|
|
146
|
+
console.log('\nā
Analysis complete!');
|
|
147
|
+
const response = await prompts({
|
|
148
|
+
choices: [
|
|
149
|
+
{ description: 'Quick overview in terminal', title: 'Terminal Summary', value: 'terminal' },
|
|
150
|
+
{ description: 'Detailed HTML visual report', title: 'UI Dashboard (Browser)', value: 'ui' },
|
|
151
|
+
{ description: 'Serve on localhost port 8080', title: 'UI Dashboard (Hosted)', value: 'hosted' },
|
|
152
|
+
],
|
|
153
|
+
initial: 0,
|
|
154
|
+
message: 'How would you like to view the analysis report?',
|
|
155
|
+
name: 'reportType',
|
|
156
|
+
type: 'select',
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (!response.reportType) {
|
|
160
|
+
console.log('Operation cancelled.');
|
|
161
|
+
process.exit(0);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (response.reportType === 'terminal') {
|
|
165
|
+
printTerminalSummary(reportData, allFixedFiles, totalErrors, totalWarnings, typecheckResult);
|
|
166
|
+
} else if (response.reportType === 'ui') {
|
|
167
|
+
console.log('ā³ Generating UI Dashboard...');
|
|
168
|
+
const outputPath = generateHtmlReport(reportData, allFixedFiles);
|
|
169
|
+
console.log(`š Opening report in browser: ${outputPath}`);
|
|
170
|
+
await open(outputPath);
|
|
171
|
+
} else if (response.reportType === 'hosted') {
|
|
172
|
+
console.log('ā³ Generating Hosted UI Dashboard...');
|
|
173
|
+
const templatePath = path.join(configDir, 'report-template.html');
|
|
174
|
+
let html = fs.readFileSync(templatePath, 'utf-8');
|
|
175
|
+
html = html.replace('/* REPORT_DATA_PLACEHOLDER */ null', JSON.stringify(reportData));
|
|
176
|
+
html = html.replace('/* FIXED_FILES_PLACEHOLDER */ []', JSON.stringify(allFixedFiles));
|
|
177
|
+
serveHostedReport(html);
|
|
178
|
+
}
|
|
179
|
+
} catch (globalError) {
|
|
180
|
+
console.error('\nā tz-clean encountered a fatal error:', globalError.message);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function runEslintTools(includePaths = [], excludePaths = []) {
|
|
186
|
+
let eslintOutput = '';
|
|
187
|
+
let reportData = [];
|
|
188
|
+
|
|
189
|
+
const targets = includePaths.length > 0 ? includePaths.map((p) => `"${p}"`).join(' ') : '.';
|
|
190
|
+
const ignoreFlags = excludePaths.map((p) => `--ignore-pattern "${p}"`).join(' ');
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
eslintOutput = execSync(
|
|
194
|
+
`npx eslint ${targets} -c "${path.join(configDir, 'eslint.config.mjs')}" -f json ${ignoreFlags}`,
|
|
195
|
+
{
|
|
196
|
+
encoding: 'utf-8',
|
|
197
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
198
|
+
},
|
|
199
|
+
);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
eslintOutput = err.stdout;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (eslintOutput) {
|
|
205
|
+
try {
|
|
206
|
+
reportData = JSON.parse(eslintOutput);
|
|
207
|
+
} catch (e) {
|
|
208
|
+
console.error('ā Failed to parse ESLint output.', e);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return reportData;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function runPrettierTools(includePaths = [], excludePaths = []) {
|
|
216
|
+
let fixedFiles = [];
|
|
217
|
+
|
|
218
|
+
const targets = includePaths.length > 0 ? includePaths.map((p) => `"${p}"`).join(' ') : '.';
|
|
219
|
+
const defaultIgnorePath = path.join(configDir, '.prettierignore');
|
|
220
|
+
let ignoreContent = fs.readFileSync(defaultIgnorePath, 'utf-8');
|
|
221
|
+
if (excludePaths.length > 0) {
|
|
222
|
+
ignoreContent += '\n# User-specified excludes\n' + excludePaths.join('\n') + '\n';
|
|
223
|
+
}
|
|
224
|
+
const tempIgnorePath = path.join(targetDir, '.tz-clean-prettierignore');
|
|
225
|
+
fs.writeFileSync(tempIgnorePath, ignoreContent);
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const prettierOutput = execSync(
|
|
229
|
+
`npx prettier --write ${targets} --config "${path.join(configDir, '.prettierrc')}" --ignore-path "${tempIgnorePath}" --ignore-unknown`,
|
|
230
|
+
{ encoding: 'utf-8' },
|
|
231
|
+
);
|
|
232
|
+
fixedFiles = prettierOutput
|
|
233
|
+
.split('\n')
|
|
234
|
+
.filter((line) => line.trim() && !line.includes('(unchanged)'))
|
|
235
|
+
.map((line) => line.split(' ')[0]);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
if (err.stdout) {
|
|
238
|
+
fixedFiles = err.stdout
|
|
239
|
+
.split('\n')
|
|
240
|
+
.filter((line) => line.trim() && !line.includes('(unchanged)'))
|
|
241
|
+
.map((line) => line.split(' ')[0]);
|
|
242
|
+
}
|
|
243
|
+
} finally {
|
|
244
|
+
if (fs.existsSync(tempIgnorePath)) fs.unlinkSync(tempIgnorePath);
|
|
245
|
+
}
|
|
246
|
+
return fixedFiles;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function runTypecheckTools() {
|
|
250
|
+
let typecheckError = null;
|
|
251
|
+
let ran = false;
|
|
252
|
+
if (fs.existsSync(path.join(targetDir, 'tsconfig.json'))) {
|
|
253
|
+
ran = true;
|
|
254
|
+
try {
|
|
255
|
+
execSync(`npx tsc --noEmit`, { stdio: 'ignore' });
|
|
256
|
+
} catch {
|
|
257
|
+
typecheckError = 'TypeScript validation failed. Check your types.';
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return { error: typecheckError, ran };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function serveHostedReport(html) {
|
|
264
|
+
const port = 8080;
|
|
265
|
+
const server = http.createServer((req, res) => {
|
|
266
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
267
|
+
res.end(html);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
server.listen(port, async () => {
|
|
271
|
+
const url = `http://localhost:${port}`;
|
|
272
|
+
console.log(`š Server running at ${url}`);
|
|
273
|
+
console.log('Press Ctrl+C to stop the server.');
|
|
274
|
+
await open(url);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function skipLineComment(source, startIndex) {
|
|
279
|
+
let i = startIndex + 2;
|
|
280
|
+
const len = source.length;
|
|
281
|
+
while (i < len && source[i] !== '\n') i++;
|
|
282
|
+
return i;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function skipStringLiteral(source, startIndex) {
|
|
286
|
+
const quote = source[startIndex];
|
|
287
|
+
let i = startIndex + 1;
|
|
288
|
+
const len = source.length;
|
|
289
|
+
while (i < len) {
|
|
290
|
+
const sc = source[i];
|
|
291
|
+
if (sc === '\\') {
|
|
292
|
+
i += 2;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
i++;
|
|
296
|
+
if (sc === quote) break;
|
|
297
|
+
}
|
|
298
|
+
return i;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function stripBlockComment(source, startIndex) {
|
|
302
|
+
let i = startIndex + 2;
|
|
303
|
+
const len = source.length;
|
|
304
|
+
while (i < len) {
|
|
305
|
+
if (source[i] === '*' && source[i + 1] === '/') return i + 2;
|
|
306
|
+
i++;
|
|
307
|
+
}
|
|
308
|
+
return i;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function stripCommentsAndEmptyLines(includePaths = [], excludePaths = []) {
|
|
312
|
+
const extensions = ['.cjs', '.js', '.jsx', '.mjs', '.ts', '.tsx'];
|
|
313
|
+
const ignorePatterns = buildIgnorePatterns(excludePaths);
|
|
314
|
+
const modifiedFiles = [];
|
|
315
|
+
|
|
316
|
+
const scanDir = (dir) => {
|
|
317
|
+
let entries;
|
|
318
|
+
try {
|
|
319
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
320
|
+
} catch {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
for (const entry of entries) {
|
|
324
|
+
const fullPath = path.join(dir, entry.name);
|
|
325
|
+
if (ignorePatterns.some((re) => re.test(fullPath))) continue;
|
|
326
|
+
if (entry.isDirectory()) {
|
|
327
|
+
scanDir(fullPath);
|
|
328
|
+
} else if (entry.isFile() && extensions.includes(path.extname(entry.name))) {
|
|
329
|
+
const original = fs.readFileSync(fullPath, 'utf-8');
|
|
330
|
+
let cleaned = stripCommentsFromSource(original);
|
|
331
|
+
cleaned = cleaned.replace(new RegExp('\\n{3,}', 'g'), '\n\n');
|
|
332
|
+
cleaned = cleaned.replace(new RegExp('^\\n+'), '');
|
|
333
|
+
cleaned = cleaned.trimEnd() + '\n';
|
|
334
|
+
if (cleaned !== original) {
|
|
335
|
+
fs.writeFileSync(fullPath, cleaned, 'utf-8');
|
|
336
|
+
modifiedFiles.push(path.relative(targetDir, fullPath));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const targets = includePaths.length > 0 ? includePaths.map((p) => path.resolve(targetDir, p)) : [targetDir];
|
|
343
|
+
targets.forEach((t) => scanDir(t));
|
|
344
|
+
return modifiedFiles;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function stripCommentsFromSource(source) {
|
|
348
|
+
let result = '';
|
|
349
|
+
let i = 0;
|
|
350
|
+
const len = source.length;
|
|
351
|
+
|
|
352
|
+
if (source.startsWith('#!')) {
|
|
353
|
+
const end = source.indexOf('\n');
|
|
354
|
+
const shebanEnd = end === -1 ? len : end + 1;
|
|
355
|
+
result += source.slice(0, shebanEnd);
|
|
356
|
+
i = shebanEnd;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
while (i < len) {
|
|
360
|
+
const ch = source[i];
|
|
361
|
+
|
|
362
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
363
|
+
const start = i;
|
|
364
|
+
i = skipStringLiteral(source, i);
|
|
365
|
+
result += source.slice(start, i);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (ch === '/' && source[i + 1] === '*') {
|
|
370
|
+
i = stripBlockComment(source, i);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (ch === '/' && source[i + 1] === '/') {
|
|
375
|
+
i = skipLineComment(source, i);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
result += ch;
|
|
380
|
+
i++;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
run();
|