tz-clean 2.0.0 โ 2.0.2
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 +94 -94
- package/.github/workflows/ci.yml +42 -42
- package/.husky/commit-msg +1 -1
- package/.husky/pre-commit +1 -1
- package/.husky/pre-push +3 -3
- package/.prettierrc +9 -9
- package/README-DEV.md +123 -123
- package/README-USER.md +56 -56
- 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 +52 -52
- package/index.js +262 -261
- package/knip.json +5 -5
- package/package.json +67 -67
- package/report-template.html +357 -357
- package/tsconfig.json +16 -16
- package/.tz-clean-report.html +0 -413
package/index.js
CHANGED
|
@@ -1,261 +1,262 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*
|
|
17
|
-
* @
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
* @param {
|
|
33
|
-
* @
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
html = html.replace('/*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*
|
|
68
|
-
* @param {
|
|
69
|
-
* @param {
|
|
70
|
-
* @param {number}
|
|
71
|
-
* @param {
|
|
72
|
-
* @
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
console.log(
|
|
77
|
-
console.log(`
|
|
78
|
-
console.log(`
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
*
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{ description: '
|
|
136
|
-
{ description: '
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
html =
|
|
161
|
-
html = html.replace('/*
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
*
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let
|
|
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
|
-
let
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
*
|
|
244
|
-
* @
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
res.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
console.log(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
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
|
+
* Calculates the total number of errors and warnings from the ESLint report data.
|
|
17
|
+
* @param {Array<object>} reportData - The report data from ESLint.
|
|
18
|
+
* @returns {{totalErrors: number, totalWarnings: number}} The calculated totals.
|
|
19
|
+
*/
|
|
20
|
+
function calculateTotals(reportData) {
|
|
21
|
+
let totalErrors = 0;
|
|
22
|
+
let totalWarnings = 0;
|
|
23
|
+
reportData.forEach((file) => {
|
|
24
|
+
totalErrors += file.errorCount;
|
|
25
|
+
totalWarnings += file.warningCount;
|
|
26
|
+
});
|
|
27
|
+
return { totalErrors, totalWarnings };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generates an HTML dashboard report.
|
|
32
|
+
* @param {Array<object>} reportData - The report data from ESLint.
|
|
33
|
+
* @param {string[]} fixedFiles - The list of formatted files.
|
|
34
|
+
* @returns {string} The output path of the generated HTML file.
|
|
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('/* REPORT_DATA_PLACEHOLDER */ null', JSON.stringify(reportData));
|
|
41
|
+
html = html.replace('/* FIXED_FILES_PLACEHOLDER */ []', JSON.stringify(fixedFiles));
|
|
42
|
+
|
|
43
|
+
const outputPath = path.join(targetDir, '.tz-clean-report.html');
|
|
44
|
+
fs.writeFileSync(outputPath, html);
|
|
45
|
+
return outputPath;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parses command line arguments to determine which tools to run.
|
|
50
|
+
* @returns {{runEslint: boolean, runPrettier: boolean, runTypecheck: boolean, tools: string[]}} The configuration object.
|
|
51
|
+
*/
|
|
52
|
+
function parseArgs() {
|
|
53
|
+
const args = process.argv.slice(2);
|
|
54
|
+
const runPrettier = !args.includes('--no-prettier');
|
|
55
|
+
const runEslint = !args.includes('--no-eslint');
|
|
56
|
+
const runTypecheck = !args.includes('--no-typecheck');
|
|
57
|
+
|
|
58
|
+
const tools = [];
|
|
59
|
+
if (runPrettier) tools.push('Prettier');
|
|
60
|
+
if (runEslint) tools.push('ESLint');
|
|
61
|
+
if (runTypecheck) tools.push('Typecheck');
|
|
62
|
+
|
|
63
|
+
return { runEslint, runPrettier, runTypecheck, tools };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Prints a summary of the analysis report to the terminal.
|
|
68
|
+
* @param {Array<object>} reportData - The report data from ESLint.
|
|
69
|
+
* @param {string[]} fixedFiles - The list of formatted files.
|
|
70
|
+
* @param {number} totalErrors - Total ESLint errors.
|
|
71
|
+
* @param {number} totalWarnings - Total ESLint warnings.
|
|
72
|
+
* @param {{error: string|null, ran: boolean}} typecheckResult - The typecheck execution result.
|
|
73
|
+
* @returns {void}
|
|
74
|
+
*/
|
|
75
|
+
function printTerminalSummary(reportData, fixedFiles, totalErrors, totalWarnings, typecheckResult) {
|
|
76
|
+
console.log('\n--- ๐ tz-clean Summary ---');
|
|
77
|
+
console.log(`Files Analyzed: ${reportData.length}`);
|
|
78
|
+
console.log(`Errors (Critical) : ${totalErrors > 0 ? 'โ ' + totalErrors : 'โ
0'}`);
|
|
79
|
+
console.log(`Warnings : ${totalWarnings > 0 ? 'โ ๏ธ ' + totalWarnings : 'โ
0'}`);
|
|
80
|
+
|
|
81
|
+
if (!typecheckResult.ran) {
|
|
82
|
+
console.log(`Typecheck : โ ๏ธ Skipped`);
|
|
83
|
+
} else if (typecheckResult.error) {
|
|
84
|
+
console.log(`Typecheck : โ ${typecheckResult.error}`);
|
|
85
|
+
} else {
|
|
86
|
+
console.log(`Typecheck : โ
Passed`);
|
|
87
|
+
}
|
|
88
|
+
console.log('---------------------------');
|
|
89
|
+
|
|
90
|
+
if (fixedFiles.length > 0) {
|
|
91
|
+
console.log('\nโจ Auto-Fixed (Formatted):');
|
|
92
|
+
fixedFiles.forEach((file) => console.log(` - ${file}`));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (totalErrors > 0 || totalWarnings > 0) {
|
|
96
|
+
console.log('\n๐ Details:');
|
|
97
|
+
reportData.forEach((file) => {
|
|
98
|
+
if (file.errorCount > 0 || file.warningCount > 0) {
|
|
99
|
+
console.log(`\n๐ ${path.relative(targetDir, file.filePath) || file.filePath}`);
|
|
100
|
+
file.messages.forEach((msg) => {
|
|
101
|
+
const severity = msg.severity === 2 ? 'โ Error' : 'โ ๏ธ Warning';
|
|
102
|
+
console.log(` ${severity} [${msg.line || 0}:${msg.column || 0}] ${msg.message} (${msg.ruleId || ''})`);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
console.log('\n---------------------------\n');
|
|
107
|
+
} else {
|
|
108
|
+
console.log('\n');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Orchestrates the overall execution of the CLI.
|
|
114
|
+
* @returns {Promise<void>}
|
|
115
|
+
*/
|
|
116
|
+
async function run() {
|
|
117
|
+
const { runEslint, runPrettier, runTypecheck, tools } = parseArgs();
|
|
118
|
+
|
|
119
|
+
if (tools.length === 0) {
|
|
120
|
+
console.log('No tools selected to run.');
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(`๐งน Running tz-clean (${tools.join(', ')})...`);
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const fixedFiles = runPrettier ? runPrettierTools() : [];
|
|
128
|
+
const typecheckResult = runTypecheck ? runTypecheckTools() : { error: null, ran: false };
|
|
129
|
+
const reportData = runEslint ? runEslintTools() : [];
|
|
130
|
+
const { totalErrors, totalWarnings } = calculateTotals(reportData);
|
|
131
|
+
|
|
132
|
+
console.log('\nโ
Analysis complete!');
|
|
133
|
+
const response = await prompts({
|
|
134
|
+
choices: [
|
|
135
|
+
{ description: 'Quick overview in terminal', title: 'Terminal Summary', value: 'terminal' },
|
|
136
|
+
{ description: 'Detailed HTML visual report', title: 'UI Dashboard (Browser)', value: 'ui' },
|
|
137
|
+
{ description: 'Serve on localhost port 8080', title: 'UI Dashboard (Hosted)', value: 'hosted' },
|
|
138
|
+
],
|
|
139
|
+
initial: 0,
|
|
140
|
+
message: 'How would you like to view the analysis report?',
|
|
141
|
+
name: 'reportType',
|
|
142
|
+
type: 'select',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (!response.reportType) {
|
|
146
|
+
console.log('Operation cancelled.');
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (response.reportType === 'terminal') {
|
|
151
|
+
printTerminalSummary(reportData, fixedFiles, totalErrors, totalWarnings, typecheckResult);
|
|
152
|
+
} else if (response.reportType === 'ui') {
|
|
153
|
+
console.log('โณ Generating UI Dashboard...');
|
|
154
|
+
const outputPath = generateHtmlReport(reportData, fixedFiles);
|
|
155
|
+
console.log(`๐ Opening report in browser: ${outputPath}`);
|
|
156
|
+
await open(outputPath);
|
|
157
|
+
} else if (response.reportType === 'hosted') {
|
|
158
|
+
console.log('โณ Generating Hosted UI Dashboard...');
|
|
159
|
+
const templatePath = path.join(configDir, 'report-template.html');
|
|
160
|
+
let html = fs.readFileSync(templatePath, 'utf-8');
|
|
161
|
+
html = html.replace('/* REPORT_DATA_PLACEHOLDER */ null', JSON.stringify(reportData));
|
|
162
|
+
html = html.replace('/* FIXED_FILES_PLACEHOLDER */ []', JSON.stringify(fixedFiles));
|
|
163
|
+
serveHostedReport(html);
|
|
164
|
+
}
|
|
165
|
+
} catch (globalError) {
|
|
166
|
+
console.error('\nโ tz-clean encountered a fatal error:', globalError.message);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Executes ESLint to analyze code.
|
|
173
|
+
* @returns {Array<object>} An array containing the report data from ESLint.
|
|
174
|
+
*/
|
|
175
|
+
function runEslintTools() {
|
|
176
|
+
let eslintOutput = '';
|
|
177
|
+
let reportData = [];
|
|
178
|
+
try {
|
|
179
|
+
eslintOutput = execSync(`npx eslint . --fix -c "${path.join(configDir, 'eslint.config.mjs')}" -f json`, {
|
|
180
|
+
encoding: 'utf-8',
|
|
181
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
182
|
+
});
|
|
183
|
+
} catch (err) {
|
|
184
|
+
eslintOutput = err.stdout;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (eslintOutput) {
|
|
188
|
+
try {
|
|
189
|
+
reportData = JSON.parse(eslintOutput);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
console.error('โ Failed to parse ESLint output.', e);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return reportData;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Executes Prettier to format files.
|
|
200
|
+
* @returns {string[]} An array of files that were fixed.
|
|
201
|
+
*/
|
|
202
|
+
function runPrettierTools() {
|
|
203
|
+
let fixedFiles = [];
|
|
204
|
+
try {
|
|
205
|
+
const prettierOutput = execSync(
|
|
206
|
+
`npx prettier --write . --config "${path.join(configDir, '.prettierrc')}" --ignore-unknown`,
|
|
207
|
+
{ encoding: 'utf-8' },
|
|
208
|
+
);
|
|
209
|
+
fixedFiles = prettierOutput
|
|
210
|
+
.split('\n')
|
|
211
|
+
.filter((line) => line.trim() && !line.includes('(unchanged)'))
|
|
212
|
+
.map((line) => line.split(' ')[0]);
|
|
213
|
+
} catch (err) {
|
|
214
|
+
if (err.stdout) {
|
|
215
|
+
fixedFiles = err.stdout
|
|
216
|
+
.split('\n')
|
|
217
|
+
.filter((line) => line.trim() && !line.includes('(unchanged)'))
|
|
218
|
+
.map((line) => line.split(' ')[0]);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return fixedFiles;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Executes TypeScript type checking.
|
|
226
|
+
* @returns {{error: string|null, ran: boolean}} The result of type checking.
|
|
227
|
+
*/
|
|
228
|
+
function runTypecheckTools() {
|
|
229
|
+
let typecheckError = null;
|
|
230
|
+
let ran = false;
|
|
231
|
+
if (fs.existsSync(path.join(targetDir, 'tsconfig.json'))) {
|
|
232
|
+
ran = true;
|
|
233
|
+
try {
|
|
234
|
+
execSync(`npx tsc --noEmit`, { stdio: 'ignore' });
|
|
235
|
+
} catch {
|
|
236
|
+
typecheckError = 'TypeScript validation failed. Check your types.';
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return { error: typecheckError, ran };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Serves the HTML report on localhost.
|
|
244
|
+
* @param {string} html - The HTML string content.
|
|
245
|
+
* @returns {void}
|
|
246
|
+
*/
|
|
247
|
+
function serveHostedReport(html) {
|
|
248
|
+
const port = 8080;
|
|
249
|
+
const server = http.createServer((req, res) => {
|
|
250
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
251
|
+
res.end(html);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
server.listen(port, async () => {
|
|
255
|
+
const url = `http://localhost:${port}`;
|
|
256
|
+
console.log(`๐ Server running at ${url}`);
|
|
257
|
+
console.log('Press Ctrl+C to stop the server.');
|
|
258
|
+
await open(url);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
run();
|
package/knip.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://unpkg.com/knip@5/schema-json.json",
|
|
3
|
-
"entry": ["index.js", "__tests__/**/*.test.js"],
|
|
4
|
-
"project": ["**/*.js", "**/*.mjs"]
|
|
5
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/knip@5/schema-json.json",
|
|
3
|
+
"entry": ["index.js", "__tests__/**/*.test.js"],
|
|
4
|
+
"project": ["**/*.js", "**/*.mjs"]
|
|
5
|
+
}
|