@tng-sh/js 0.2.4 ā 0.2.5
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/bin/tng.js +61 -9
- package/lib/generateTestsUi.js +38 -29
- package/lib/ignore.js +56 -0
- package/package.json +1 -1
- package/tng_sh_js.linux-arm64-gnu.node +0 -0
- package/tng_sh_js.linux-x64-gnu.node +0 -0
package/bin/tng.js
CHANGED
|
@@ -5,6 +5,7 @@ const chalk = require('chalk');
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { loadConfig } = require('../lib/config');
|
|
8
|
+
const { isIgnoredPath, filterIgnoredPaths, ignoredMessage } = require('../lib/ignore');
|
|
8
9
|
const { saveTestFile } = require('../lib/saveFile');
|
|
9
10
|
const { ping, getUserStats } = require('../index');
|
|
10
11
|
const { applyFix } = require('../lib/fixApplier');
|
|
@@ -28,7 +29,7 @@ process.on('uncaughtException', (err) => {
|
|
|
28
29
|
program
|
|
29
30
|
.name('tng')
|
|
30
31
|
.description('TNG - Advanced Code Audit, Test Generation, Visualization, Clone Detection, and Dead Code Analysis for JavaScript/TypeScript')
|
|
31
|
-
.version('0.2.
|
|
32
|
+
.version('0.2.5');
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Copy text to system clipboard
|
|
@@ -92,7 +93,11 @@ module.exports = {
|
|
|
92
93
|
TEST_FRAMEWORK: "jest",
|
|
93
94
|
|
|
94
95
|
// Test Directory
|
|
95
|
-
TEST_DIRECTORY: "tests"
|
|
96
|
+
TEST_DIRECTORY: "tests",
|
|
97
|
+
|
|
98
|
+
// Ignore paths (relative to project root)
|
|
99
|
+
IGNORE_FILES: [],
|
|
100
|
+
IGNORE_FOLDERS: []
|
|
96
101
|
};
|
|
97
102
|
`;
|
|
98
103
|
fs.writeFileSync(configPath, content);
|
|
@@ -189,11 +194,16 @@ program
|
|
|
189
194
|
process.exit(1);
|
|
190
195
|
}
|
|
191
196
|
|
|
197
|
+
const config = loadConfig();
|
|
198
|
+
const resolvedPath = path.resolve(options.file);
|
|
199
|
+
if (isIgnoredPath(resolvedPath, config)) {
|
|
200
|
+
console.log(chalk.red(ignoredMessage()));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
192
204
|
console.log(chalk.blue(`š Generating X-Ray for ${options.method} in ${options.file}...`));
|
|
193
205
|
|
|
194
206
|
try {
|
|
195
|
-
const config = loadConfig();
|
|
196
|
-
|
|
197
207
|
// Re-use logic similar to _handleXrayFlow but tailored for CLI non-interactive output
|
|
198
208
|
const { generateTest } = require('../index');
|
|
199
209
|
|
|
@@ -304,10 +314,16 @@ program
|
|
|
304
314
|
if (options.method && options.file && options.trace) {
|
|
305
315
|
const { getSymbolicTrace } = require('../index');
|
|
306
316
|
const GoUISession = require('../lib/goUiSession');
|
|
317
|
+
const config = loadConfig();
|
|
318
|
+
const resolvedPath = path.resolve(options.file);
|
|
319
|
+
if (isIgnoredPath(resolvedPath, config)) {
|
|
320
|
+
console.log(chalk.red(ignoredMessage()));
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
307
323
|
|
|
308
324
|
try {
|
|
309
325
|
const traceJson = getSymbolicTrace(
|
|
310
|
-
|
|
326
|
+
resolvedPath,
|
|
311
327
|
options.method,
|
|
312
328
|
options.class || null
|
|
313
329
|
);
|
|
@@ -344,11 +360,16 @@ program
|
|
|
344
360
|
// 2. X-Ray
|
|
345
361
|
if (options.method && options.file && options.xray) {
|
|
346
362
|
const config = loadConfig();
|
|
363
|
+
const resolvedPath = path.resolve(options.file);
|
|
364
|
+
if (isIgnoredPath(resolvedPath, config)) {
|
|
365
|
+
console.log(chalk.red(ignoredMessage()));
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
347
368
|
const { generateTest: nativeGenerateTest } = require('../index');
|
|
348
369
|
|
|
349
370
|
try {
|
|
350
371
|
const resultJson = nativeGenerateTest(
|
|
351
|
-
|
|
372
|
+
resolvedPath,
|
|
352
373
|
options.method,
|
|
353
374
|
options.class || null,
|
|
354
375
|
'visualize',
|
|
@@ -429,6 +450,7 @@ program
|
|
|
429
450
|
*/
|
|
430
451
|
async function runClones(filePath, level, jsonMode = false) {
|
|
431
452
|
const { analyzeClones } = require('../index');
|
|
453
|
+
const config = loadConfig();
|
|
432
454
|
const absolutePath = path.resolve(filePath);
|
|
433
455
|
const projectRoot = process.cwd();
|
|
434
456
|
|
|
@@ -436,6 +458,10 @@ async function runClones(filePath, level, jsonMode = false) {
|
|
|
436
458
|
console.log(chalk.red(`File not found: ${filePath}`));
|
|
437
459
|
process.exit(1);
|
|
438
460
|
}
|
|
461
|
+
if (isIgnoredPath(absolutePath, config)) {
|
|
462
|
+
console.log(chalk.red(ignoredMessage()));
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
439
465
|
|
|
440
466
|
const GoUISession = require('../lib/goUiSession');
|
|
441
467
|
const session = new GoUISession();
|
|
@@ -485,12 +511,17 @@ async function runClones(filePath, level, jsonMode = false) {
|
|
|
485
511
|
*/
|
|
486
512
|
async function runDeadCode(filePath, jsonMode = false) {
|
|
487
513
|
const { detectDeadCode } = require('../index');
|
|
514
|
+
const config = loadConfig();
|
|
488
515
|
const absolutePath = path.resolve(filePath);
|
|
489
516
|
|
|
490
517
|
if (!fs.existsSync(absolutePath)) {
|
|
491
518
|
console.log(chalk.red(`File not found: ${filePath}`));
|
|
492
519
|
process.exit(1);
|
|
493
520
|
}
|
|
521
|
+
if (isIgnoredPath(absolutePath, config)) {
|
|
522
|
+
console.log(chalk.red(ignoredMessage()));
|
|
523
|
+
process.exit(1);
|
|
524
|
+
}
|
|
494
525
|
|
|
495
526
|
const GoUISession = require('../lib/goUiSession');
|
|
496
527
|
const session = new GoUISession();
|
|
@@ -531,13 +562,14 @@ async function runDeadCode(filePath, jsonMode = false) {
|
|
|
531
562
|
}
|
|
532
563
|
}
|
|
533
564
|
|
|
534
|
-
function listDeadCodeFiles(projectRoot) {
|
|
565
|
+
function listDeadCodeFiles(projectRoot, config) {
|
|
535
566
|
const { listDeadcodeFiles } = require('../index');
|
|
536
567
|
try {
|
|
537
568
|
const filesJson = listDeadcodeFiles(projectRoot);
|
|
538
569
|
const files = JSON.parse(filesJson);
|
|
539
570
|
if (!Array.isArray(files)) return [];
|
|
540
|
-
|
|
571
|
+
const withoutTypes = files.filter((f) => !f.endsWith('.d.ts'));
|
|
572
|
+
return filterIgnoredPaths(withoutTypes, config, projectRoot);
|
|
541
573
|
} catch (e) {
|
|
542
574
|
return [];
|
|
543
575
|
}
|
|
@@ -546,7 +578,8 @@ function listDeadCodeFiles(projectRoot) {
|
|
|
546
578
|
async function runDeadCodeRepo(jsonMode = false) {
|
|
547
579
|
const { detectDeadCode } = require('../index');
|
|
548
580
|
const projectRoot = process.cwd();
|
|
549
|
-
const
|
|
581
|
+
const config = loadConfig();
|
|
582
|
+
const files = listDeadCodeFiles(projectRoot, config);
|
|
550
583
|
|
|
551
584
|
if (files.length === 0) {
|
|
552
585
|
console.log(chalk.yellow('No files found for dead code analysis.'));
|
|
@@ -739,6 +772,16 @@ async function generateTest(filePath, methodName, testType, auditMode = false, j
|
|
|
739
772
|
}
|
|
740
773
|
process.exit(1);
|
|
741
774
|
}
|
|
775
|
+
if (isIgnoredPath(absolutePath, config)) {
|
|
776
|
+
const message = ignoredMessage();
|
|
777
|
+
if (jsonSession) {
|
|
778
|
+
jsonSession.displayError(message);
|
|
779
|
+
jsonSession.stop();
|
|
780
|
+
} else {
|
|
781
|
+
console.log(chalk.red(message));
|
|
782
|
+
}
|
|
783
|
+
process.exit(1);
|
|
784
|
+
}
|
|
742
785
|
|
|
743
786
|
const { runAudit, generateTest: nativeGenerateTest } = require('../index');
|
|
744
787
|
|
|
@@ -847,6 +890,10 @@ program.on('--help', () => {
|
|
|
847
890
|
console.log(' 3: Fuzzy (Fuzzy structural, catches patterns with small variations)');
|
|
848
891
|
console.log(' all: Runs all detection levels (default)');
|
|
849
892
|
console.log('');
|
|
893
|
+
console.log('Ignore Paths (from tng.config.js):');
|
|
894
|
+
console.log(' IGNORE_FILES: [ "relative/path/file.js", ... ]');
|
|
895
|
+
console.log(' IGNORE_FOLDERS: [ "relative/path/folder", ... ]');
|
|
896
|
+
console.log('');
|
|
850
897
|
console.log('X-Ray Visualization:');
|
|
851
898
|
console.log(' tng src/components/MyComponent.js render --xray');
|
|
852
899
|
console.log(' tng --file=api/handler.js --method=post --xray --json');
|
|
@@ -859,6 +906,7 @@ program.on('--help', () => {
|
|
|
859
906
|
*/
|
|
860
907
|
async function runImpact(filePath, methodName, jsonMode = false, className = null) {
|
|
861
908
|
const { analyzeImpact } = require('../index');
|
|
909
|
+
const config = loadConfig();
|
|
862
910
|
const absolutePath = path.resolve(filePath);
|
|
863
911
|
const projectRoot = process.cwd();
|
|
864
912
|
|
|
@@ -866,6 +914,10 @@ async function runImpact(filePath, methodName, jsonMode = false, className = nul
|
|
|
866
914
|
console.log(chalk.red(`File not found: ${filePath}`));
|
|
867
915
|
process.exit(1);
|
|
868
916
|
}
|
|
917
|
+
if (isIgnoredPath(absolutePath, config)) {
|
|
918
|
+
console.log(chalk.red(ignoredMessage()));
|
|
919
|
+
process.exit(1);
|
|
920
|
+
}
|
|
869
921
|
|
|
870
922
|
if (jsonMode) {
|
|
871
923
|
try {
|
package/lib/generateTestsUi.js
CHANGED
|
@@ -5,6 +5,7 @@ const chalk = require('chalk');
|
|
|
5
5
|
const GoUISession = require('./goUiSession');
|
|
6
6
|
const { getFileOutline, generateTest, ping, getUserStats } = require('../index');
|
|
7
7
|
const { loadConfig } = require('./config');
|
|
8
|
+
const { filterIgnoredPaths } = require('./ignore');
|
|
8
9
|
const { saveTestFile } = require('./saveFile');
|
|
9
10
|
|
|
10
11
|
class GenerateTestsUI {
|
|
@@ -130,15 +131,19 @@ class GenerateTestsUI {
|
|
|
130
131
|
else if (isXray) title = 'Select File for X-Ray';
|
|
131
132
|
else if (isTrace) title = 'Select File for Symbolic Trace';
|
|
132
133
|
|
|
133
|
-
|
|
134
|
+
while (true) {
|
|
135
|
+
const selectedName = this.goUiSession.showListView(title, items);
|
|
134
136
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
if (selectedName === 'back') return 'back';
|
|
138
|
+
if (!selectedName || selectedName === 'exit') return 'exit';
|
|
137
139
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
const selectedFile = path.resolve(cwd, selectedName);
|
|
141
|
+
const result = await this._showMethodsForFile(selectedFile, isAudit, isXray, isTrace, isImpact);
|
|
142
|
+
|
|
143
|
+
if (result === 'main_menu') return 'main_menu';
|
|
144
|
+
if (result === 'exit') return 'exit';
|
|
145
|
+
// If result is 'back', we loop again
|
|
146
|
+
}
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
async _showMethodsForFile(filePath, isAudit = false, isXray = false, isTrace = false, isImpact = false) {
|
|
@@ -148,13 +153,13 @@ class GenerateTestsUI {
|
|
|
148
153
|
outline = JSON.parse(result);
|
|
149
154
|
} catch (e) {
|
|
150
155
|
console.error(chalk.red(`\nError parsing file: ${e.message}\n`));
|
|
151
|
-
return
|
|
156
|
+
return 'back';
|
|
152
157
|
}
|
|
153
158
|
|
|
154
159
|
const methods = outline.methods || [];
|
|
155
160
|
if (methods.length === 0) {
|
|
156
161
|
this.goUiSession.showNoItems('methods');
|
|
157
|
-
return
|
|
162
|
+
return 'back';
|
|
158
163
|
}
|
|
159
164
|
|
|
160
165
|
const fileName = path.basename(filePath);
|
|
@@ -172,36 +177,36 @@ class GenerateTestsUI {
|
|
|
172
177
|
|
|
173
178
|
const selectedDisplay = this.goUiSession.showListView(title, items);
|
|
174
179
|
|
|
175
|
-
if (selectedDisplay === 'back' || !selectedDisplay) return
|
|
180
|
+
if (selectedDisplay === 'back' || !selectedDisplay) return 'back';
|
|
176
181
|
|
|
177
182
|
const selectedMethod = items.find(i => i.name === selectedDisplay)?.methodData;
|
|
178
183
|
|
|
179
184
|
if (selectedMethod) {
|
|
180
185
|
if (isTrace) {
|
|
181
186
|
await this._launchTrace(filePath, selectedMethod.name, selectedMethod.class_name || null);
|
|
182
|
-
return
|
|
187
|
+
return 'back';
|
|
183
188
|
}
|
|
184
189
|
|
|
185
190
|
if (isXray) {
|
|
186
191
|
const choice = await this._generateTestsForMethod(filePath, selectedMethod, 'visualize', false, true);
|
|
187
192
|
if (choice === 'main_menu') return 'main_menu';
|
|
188
|
-
return
|
|
193
|
+
return 'back';
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
if (isImpact) {
|
|
192
197
|
const choice = await this._generateTestsForMethod(filePath, selectedMethod, null, false, false, true);
|
|
193
198
|
if (choice === 'main_menu') return 'main_menu';
|
|
194
|
-
return
|
|
199
|
+
return 'back';
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
if (isAudit) {
|
|
198
203
|
const choice = await this._generateTestsForMethod(filePath, selectedMethod, null, true);
|
|
199
204
|
if (choice === 'main_menu') return 'main_menu';
|
|
200
|
-
return
|
|
205
|
+
return 'back';
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
const testType = this.goUiSession.showJsTestMenu();
|
|
204
|
-
if (testType === 'back') return
|
|
209
|
+
if (testType === 'back') return 'back';
|
|
205
210
|
|
|
206
211
|
const finalType = testType === 'auto' ? null : testType;
|
|
207
212
|
const choice = await this._generateTestsForMethod(filePath, selectedMethod, finalType, false);
|
|
@@ -210,7 +215,7 @@ class GenerateTestsUI {
|
|
|
210
215
|
this._showPostGenerationMenu(choice);
|
|
211
216
|
}
|
|
212
217
|
}
|
|
213
|
-
return
|
|
218
|
+
return 'back';
|
|
214
219
|
}
|
|
215
220
|
|
|
216
221
|
async _generateTestsForMethod(filePath, method, testType, isAudit = false, isXray = false, isImpact = false) {
|
|
@@ -249,7 +254,7 @@ class GenerateTestsUI {
|
|
|
249
254
|
const { analyzeImpact } = require('../index');
|
|
250
255
|
const projectRoot = process.cwd();
|
|
251
256
|
|
|
252
|
-
console.log(chalk.blue(`\
|
|
257
|
+
console.log(chalk.blue(`\nAnalyzing impact for ${method.name}...`));
|
|
253
258
|
|
|
254
259
|
let impactResult;
|
|
255
260
|
try {
|
|
@@ -280,7 +285,7 @@ class GenerateTestsUI {
|
|
|
280
285
|
const fileName = path.basename(filePath);
|
|
281
286
|
const displayName = method.class_name ? `${method.class_name}#${method.name}` : `${fileName}#${method.name}`;
|
|
282
287
|
|
|
283
|
-
console.log(chalk.blue(`\
|
|
288
|
+
console.log(chalk.blue(`\nAuditing ${displayName}...`));
|
|
284
289
|
|
|
285
290
|
let auditResult;
|
|
286
291
|
try {
|
|
@@ -571,7 +576,7 @@ class GenerateTestsUI {
|
|
|
571
576
|
}
|
|
572
577
|
}
|
|
573
578
|
} catch (e) {
|
|
574
|
-
console.log(chalk.yellow(`\n
|
|
579
|
+
console.log(chalk.yellow(`\n Failed to open editor: ${e.message}`));
|
|
575
580
|
}
|
|
576
581
|
}
|
|
577
582
|
|
|
@@ -588,11 +593,13 @@ class GenerateTestsUI {
|
|
|
588
593
|
];
|
|
589
594
|
|
|
590
595
|
try {
|
|
591
|
-
|
|
596
|
+
const files = await glob(patterns, {
|
|
592
597
|
ignore,
|
|
593
598
|
absolute: true,
|
|
594
599
|
onlyFiles: true
|
|
595
600
|
});
|
|
601
|
+
const config = loadConfig();
|
|
602
|
+
return filterIgnoredPaths(files, config, process.cwd());
|
|
596
603
|
} catch (e) {
|
|
597
604
|
console.error(chalk.red(`\nError searching for files: ${e.message}\n`));
|
|
598
605
|
return [];
|
|
@@ -648,19 +655,21 @@ class GenerateTestsUI {
|
|
|
648
655
|
path: path.dirname(file)
|
|
649
656
|
}));
|
|
650
657
|
|
|
651
|
-
|
|
658
|
+
while (true) {
|
|
659
|
+
const selectedName = this.goUiSession.showListView('Select File for Duplicate Detection', items);
|
|
652
660
|
|
|
653
|
-
|
|
654
|
-
|
|
661
|
+
if (selectedName === 'back') return 'back';
|
|
662
|
+
if (!selectedName || selectedName === 'exit') return 'exit';
|
|
655
663
|
|
|
656
|
-
|
|
664
|
+
const selectedFile = path.resolve(cwd, selectedName);
|
|
657
665
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
666
|
+
// Show level selection
|
|
667
|
+
const level = this.goUiSession.showCloneMenu();
|
|
668
|
+
if (level === 'back') continue;
|
|
661
669
|
|
|
662
|
-
|
|
663
|
-
|
|
670
|
+
await this._runClones(selectedFile, level);
|
|
671
|
+
return 'main_menu';
|
|
672
|
+
}
|
|
664
673
|
}
|
|
665
674
|
|
|
666
675
|
async _runClones(filePath, level) {
|
package/lib/ignore.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
function normalizeEntry(entry) {
|
|
4
|
+
if (typeof entry !== 'string') return null;
|
|
5
|
+
const trimmed = entry.trim();
|
|
6
|
+
if (!trimmed) return null;
|
|
7
|
+
return trimmed.replace(/\\/g, '/');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function resolveConfigPath(entry, projectRoot) {
|
|
11
|
+
const normalized = normalizeEntry(entry);
|
|
12
|
+
if (!normalized) return null;
|
|
13
|
+
if (path.isAbsolute(normalized)) {
|
|
14
|
+
return path.resolve(normalized);
|
|
15
|
+
}
|
|
16
|
+
return path.resolve(projectRoot, normalized);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isIgnoredPath(filePath, config = {}, projectRoot = process.cwd()) {
|
|
20
|
+
if (!filePath) return false;
|
|
21
|
+
|
|
22
|
+
const absolutePath = path.resolve(filePath);
|
|
23
|
+
const ignoreFiles = Array.isArray(config.IGNORE_FILES) ? config.IGNORE_FILES : [];
|
|
24
|
+
const ignoreFolders = Array.isArray(config.IGNORE_FOLDERS) ? config.IGNORE_FOLDERS : [];
|
|
25
|
+
|
|
26
|
+
for (const entry of ignoreFiles) {
|
|
27
|
+
const resolved = resolveConfigPath(entry, projectRoot);
|
|
28
|
+
if (resolved && resolved === absolutePath) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const entry of ignoreFolders) {
|
|
34
|
+
const resolved = resolveConfigPath(entry, projectRoot);
|
|
35
|
+
if (!resolved) continue;
|
|
36
|
+
if (absolutePath === resolved) return true;
|
|
37
|
+
if (absolutePath.startsWith(resolved + path.sep)) return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function filterIgnoredPaths(paths, config = {}, projectRoot = process.cwd()) {
|
|
44
|
+
if (!Array.isArray(paths) || paths.length === 0) return [];
|
|
45
|
+
return paths.filter((p) => !isIgnoredPath(p, config, projectRoot));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function ignoredMessage() {
|
|
49
|
+
return 'Ignored by TNG config (ignore_files/ignore_folders). Remove it from config to proceed.';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
isIgnoredPath,
|
|
54
|
+
filterIgnoredPaths,
|
|
55
|
+
ignoredMessage
|
|
56
|
+
};
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|