@tng-sh/js 0.2.4 → 0.2.6

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 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.4');
32
+ .version('0.2.6');
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
- path.resolve(options.file),
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
- path.resolve(options.file),
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
- return files.filter((f) => !f.endsWith('.d.ts'));
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 files = listDeadCodeFiles(projectRoot);
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 {
Binary file
Binary file
Binary file
Binary file
package/index.d.ts CHANGED
@@ -56,6 +56,8 @@ export declare function applyEditsAtomic(operationsJson: string): string
56
56
  export declare function generateTest(filePath: string, methodName: string, className: string | undefined | null, testType: string | undefined | null, configJson: string, callback: (...args: any[]) => any): string
57
57
  /** Analyzes a method and generates a symbolic execution trace. */
58
58
  export declare function getSymbolicTrace(filePath: string, methodName: string, className?: string | undefined | null): string
59
+ /** Analyzes a method and generates a symbolic execution trace v2. */
60
+ export declare function getSymbolicTraceV2(filePath: string, methodName: string, className?: string | undefined | null, projectRoot?: string | undefined | null): string
59
61
  /**
60
62
  * Analyzes a file for code clones (duplicates).
61
63
  * Returns a JSON string containing the matches found.
package/index.js CHANGED
@@ -310,7 +310,7 @@ if (!nativeBinding) {
310
310
  throw new Error(`Failed to load native binding`)
311
311
  }
312
312
 
313
- const { getFileOutline, getProjectMetadata, findCallSites, ping, submitJob, getUserStats, runAudit, applyEdit, applyEditsAtomic, generateTest, getSymbolicTrace, analyzeClones, detectDeadCode, listDeadcodeFiles, analyzeImpact } = nativeBinding
313
+ const { getFileOutline, getProjectMetadata, findCallSites, ping, submitJob, getUserStats, runAudit, applyEdit, applyEditsAtomic, generateTest, getSymbolicTrace, getSymbolicTraceV2, analyzeClones, detectDeadCode, listDeadcodeFiles, analyzeImpact } = nativeBinding
314
314
 
315
315
  module.exports.getFileOutline = getFileOutline
316
316
  module.exports.getProjectMetadata = getProjectMetadata
@@ -323,6 +323,7 @@ module.exports.applyEdit = applyEdit
323
323
  module.exports.applyEditsAtomic = applyEditsAtomic
324
324
  module.exports.generateTest = generateTest
325
325
  module.exports.getSymbolicTrace = getSymbolicTrace
326
+ module.exports.getSymbolicTraceV2 = getSymbolicTraceV2
326
327
  module.exports.analyzeClones = analyzeClones
327
328
  module.exports.detectDeadCode = detectDeadCode
328
329
  module.exports.listDeadcodeFiles = listDeadcodeFiles
@@ -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 {
@@ -17,7 +18,6 @@ class GenerateTestsUI {
17
18
  }
18
19
 
19
20
  async show() {
20
- // Check for API key before showing menu
21
21
  const config = loadConfig();
22
22
  if (!config.API_KEY) {
23
23
  this._showConfigMissing();
@@ -94,20 +94,26 @@ class GenerateTestsUI {
94
94
 
95
95
  async _pingApi() {
96
96
  const config = loadConfig();
97
- if (!config.API_KEY) {
97
+ if (!config || !config.API_KEY) {
98
98
  console.log(chalk.red('\nNo API key configured. Run: tng init\n'));
99
- return;
99
+ return false;
100
+ }
101
+
102
+ if (!config.API_URL) {
103
+ console.log(chalk.red('\nNo API URL configured in tng.config.js.\n'));
104
+ return false;
100
105
  }
101
106
 
102
- this.goUiSession.showSpinner('Pinging TNG API...', () => {
107
+ const result = await this.goUiSession.showSpinner('Pinging TNG API...', async () => {
103
108
  try {
104
- const result = ping(config.API_URL, config.API_KEY);
105
- return { success: true, message: `API Response: ${result}` };
109
+ const res = ping(config.API_URL, config.API_KEY);
110
+ return { success: true, message: `API Response: ${res}` };
106
111
  } catch (e) {
107
112
  return { success: false, message: e.message };
108
113
  }
109
114
  });
110
- return false
115
+
116
+ return result || false;
111
117
  }
112
118
 
113
119
  async _showFileSelection(isAudit = false, isXray = false, isTrace = false, isImpact = false) {
@@ -130,15 +136,19 @@ class GenerateTestsUI {
130
136
  else if (isXray) title = 'Select File for X-Ray';
131
137
  else if (isTrace) title = 'Select File for Symbolic Trace';
132
138
 
133
- const selectedName = this.goUiSession.showListView(title, items);
139
+ while (true) {
140
+ const selectedName = this.goUiSession.showListView(title, items);
134
141
 
135
- if (selectedName === 'back') return 'back';
136
- if (!selectedName || selectedName === 'exit') return 'exit';
142
+ if (selectedName === 'back') return 'back';
143
+ if (!selectedName || selectedName === 'exit') return 'exit';
137
144
 
138
- const selectedFile = path.resolve(cwd, selectedName);
139
- const result = await this._showMethodsForFile(selectedFile, isAudit, isXray, isTrace, isImpact);
140
- if (result === 'main_menu') return 'main_menu';
141
- return result;
145
+ const selectedFile = path.resolve(cwd, selectedName);
146
+ const result = await this._showMethodsForFile(selectedFile, isAudit, isXray, isTrace, isImpact);
147
+
148
+ if (result === 'main_menu') return 'main_menu';
149
+ if (result === 'exit') return 'exit';
150
+ // If result is 'back', we loop again
151
+ }
142
152
  }
143
153
 
144
154
  async _showMethodsForFile(filePath, isAudit = false, isXray = false, isTrace = false, isImpact = false) {
@@ -148,13 +158,13 @@ class GenerateTestsUI {
148
158
  outline = JSON.parse(result);
149
159
  } catch (e) {
150
160
  console.error(chalk.red(`\nError parsing file: ${e.message}\n`));
151
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
161
+ return 'back';
152
162
  }
153
163
 
154
164
  const methods = outline.methods || [];
155
165
  if (methods.length === 0) {
156
166
  this.goUiSession.showNoItems('methods');
157
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
167
+ return 'back';
158
168
  }
159
169
 
160
170
  const fileName = path.basename(filePath);
@@ -172,36 +182,36 @@ class GenerateTestsUI {
172
182
 
173
183
  const selectedDisplay = this.goUiSession.showListView(title, items);
174
184
 
175
- if (selectedDisplay === 'back' || !selectedDisplay) return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
185
+ if (selectedDisplay === 'back' || !selectedDisplay) return 'back';
176
186
 
177
187
  const selectedMethod = items.find(i => i.name === selectedDisplay)?.methodData;
178
188
 
179
189
  if (selectedMethod) {
180
190
  if (isTrace) {
181
191
  await this._launchTrace(filePath, selectedMethod.name, selectedMethod.class_name || null);
182
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
192
+ return 'back';
183
193
  }
184
194
 
185
195
  if (isXray) {
186
196
  const choice = await this._generateTestsForMethod(filePath, selectedMethod, 'visualize', false, true);
187
197
  if (choice === 'main_menu') return 'main_menu';
188
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
198
+ return 'back';
189
199
  }
190
200
 
191
201
  if (isImpact) {
192
202
  const choice = await this._generateTestsForMethod(filePath, selectedMethod, null, false, false, true);
193
203
  if (choice === 'main_menu') return 'main_menu';
194
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
204
+ return 'back';
195
205
  }
196
206
 
197
207
  if (isAudit) {
198
208
  const choice = await this._generateTestsForMethod(filePath, selectedMethod, null, true);
199
209
  if (choice === 'main_menu') return 'main_menu';
200
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
210
+ return 'back';
201
211
  }
202
212
 
203
213
  const testType = this.goUiSession.showJsTestMenu();
204
- if (testType === 'back') return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
214
+ if (testType === 'back') return 'back';
205
215
 
206
216
  const finalType = testType === 'auto' ? null : testType;
207
217
  const choice = await this._generateTestsForMethod(filePath, selectedMethod, finalType, false);
@@ -210,7 +220,7 @@ class GenerateTestsUI {
210
220
  this._showPostGenerationMenu(choice);
211
221
  }
212
222
  }
213
- return this._showFileSelection(isAudit, isXray, isTrace, isImpact);
223
+ return 'back';
214
224
  }
215
225
 
216
226
  async _generateTestsForMethod(filePath, method, testType, isAudit = false, isXray = false, isImpact = false) {
@@ -249,7 +259,7 @@ class GenerateTestsUI {
249
259
  const { analyzeImpact } = require('../index');
250
260
  const projectRoot = process.cwd();
251
261
 
252
- console.log(chalk.blue(`\n🔍 Analyzing impact for ${method.name}...`));
262
+ console.log(chalk.blue(`\nAnalyzing impact for ${method.name}...`));
253
263
 
254
264
  let impactResult;
255
265
  try {
@@ -280,7 +290,7 @@ class GenerateTestsUI {
280
290
  const fileName = path.basename(filePath);
281
291
  const displayName = method.class_name ? `${method.class_name}#${method.name}` : `${fileName}#${method.name}`;
282
292
 
283
- console.log(chalk.blue(`\n🔍 Auditing ${displayName}...`));
293
+ console.log(chalk.blue(`\nAuditing ${displayName}...`));
284
294
 
285
295
  let auditResult;
286
296
  try {
@@ -571,7 +581,7 @@ class GenerateTestsUI {
571
581
  }
572
582
  }
573
583
  } catch (e) {
574
- console.log(chalk.yellow(`\n⚠️ Failed to open editor: ${e.message}`));
584
+ console.log(chalk.yellow(`\n Failed to open editor: ${e.message}`));
575
585
  }
576
586
  }
577
587
 
@@ -588,11 +598,13 @@ class GenerateTestsUI {
588
598
  ];
589
599
 
590
600
  try {
591
- return await glob(patterns, {
601
+ const files = await glob(patterns, {
592
602
  ignore,
593
603
  absolute: true,
594
604
  onlyFiles: true
595
605
  });
606
+ const config = loadConfig();
607
+ return filterIgnoredPaths(files, config, process.cwd());
596
608
  } catch (e) {
597
609
  console.error(chalk.red(`\nError searching for files: ${e.message}\n`));
598
610
  return [];
@@ -648,19 +660,21 @@ class GenerateTestsUI {
648
660
  path: path.dirname(file)
649
661
  }));
650
662
 
651
- const selectedName = this.goUiSession.showListView('Select File for Duplicate Detection', items);
663
+ while (true) {
664
+ const selectedName = this.goUiSession.showListView('Select File for Duplicate Detection', items);
652
665
 
653
- if (selectedName === 'back') return 'back';
654
- if (!selectedName || selectedName === 'exit') return 'exit';
666
+ if (selectedName === 'back') return 'back';
667
+ if (!selectedName || selectedName === 'exit') return 'exit';
655
668
 
656
- const selectedFile = path.resolve(cwd, selectedName);
669
+ const selectedFile = path.resolve(cwd, selectedName);
657
670
 
658
- // Show level selection
659
- const level = this.goUiSession.showCloneMenu();
660
- if (level === 'back') return this._showClonesSelection();
671
+ // Show level selection
672
+ const level = this.goUiSession.showCloneMenu();
673
+ if (level === 'back') continue;
661
674
 
662
- await this._runClones(selectedFile, level);
663
- return 'main_menu';
675
+ await this._runClones(selectedFile, level);
676
+ return 'main_menu';
677
+ }
664
678
  }
665
679
 
666
680
  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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tng-sh/js",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "TNG JavaScript CLI",
5
5
  "repository": {
6
6
  "type": "git",
Binary file
Binary file
package/.idea/js-pie.iml DELETED
@@ -1,11 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="RUBY_MODULE" version="4">
3
- <component name="ModuleRunConfigurationManager">
4
- <shared />
5
- </component>
6
- <component name="NewModuleRootManager">
7
- <content url="file://$MODULE_DIR$" />
8
- <orderEntry type="inheritedJdk" />
9
- <orderEntry type="sourceFolder" forTests="false" />
10
- </component>
11
- </module>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/js-pie.iml" filepath="$PROJECT_DIR$/.idea/js-pie.iml" />
6
- </modules>
7
- </component>
8
- </project>
package/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="" vcs="Git" />
5
- </component>
6
- </project>