@tng-sh/js 0.0.9 → 0.1.1
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 +85 -21
- package/binaries/go-ui-darwin-amd64 +0 -0
- package/binaries/go-ui-darwin-arm64 +0 -0
- package/binaries/go-ui-linux-amd64 +0 -0
- package/binaries/go-ui-linux-arm64 +0 -0
- package/index.d.ts +35 -1
- package/index.js +5 -2
- package/lib/auditWorker.js +28 -0
- package/lib/config.js +1 -0
- package/lib/fixApplier.js +59 -0
- package/lib/generateTestsUi.js +289 -98
- package/lib/goUiSession.js +140 -71
- package/lib/jsonSession.js +49 -27
- package/package.json +1 -1
- package/tng_sh_js.darwin-arm64.node +0 -0
- package/tng_sh_js.darwin-x64.node +0 -0
- 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
|
@@ -7,11 +7,28 @@ const path = require('path');
|
|
|
7
7
|
const { loadConfig } = require('../lib/config');
|
|
8
8
|
const { saveTestFile } = require('../lib/saveFile');
|
|
9
9
|
const { ping, getUserStats } = require('../index');
|
|
10
|
+
const { applyFix } = require('../lib/fixApplier');
|
|
11
|
+
|
|
12
|
+
// Handle EPIPE errors gracefully when child process (Go UI) exits
|
|
13
|
+
process.stdout.on('error', (err) => {
|
|
14
|
+
if (err.code === 'EPIPE') {
|
|
15
|
+
// Ignore - this happens when Go UI exits and we try to write to closed pipe
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
process.on('uncaughtException', (err) => {
|
|
21
|
+
if (err.code === 'EPIPE') {
|
|
22
|
+
// Ignore broken pipe - child process exited
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
throw err; // Re-throw other errors
|
|
26
|
+
});
|
|
10
27
|
|
|
11
28
|
program
|
|
12
29
|
.name('tng')
|
|
13
30
|
.description('TNG - Automated Test Generation, and audit generation for JavaScript')
|
|
14
|
-
.version('0.
|
|
31
|
+
.version('0.1.1');
|
|
15
32
|
|
|
16
33
|
/**
|
|
17
34
|
* @command init
|
|
@@ -150,6 +167,39 @@ program
|
|
|
150
167
|
}
|
|
151
168
|
});
|
|
152
169
|
|
|
170
|
+
/**
|
|
171
|
+
* @command fix
|
|
172
|
+
* Apply a specific fix to a file
|
|
173
|
+
*/
|
|
174
|
+
program
|
|
175
|
+
.command('fix')
|
|
176
|
+
.description('Apply a fix to a file')
|
|
177
|
+
.option('-f, --file <path>', 'File path to fix')
|
|
178
|
+
.option('-d, --data <json>', 'JSON data for the audit item containing fix info')
|
|
179
|
+
.action(async (options) => {
|
|
180
|
+
if (!options.file || !options.data) {
|
|
181
|
+
console.log(chalk.red('Error: Both --file and --data are required.'));
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const item = JSON.parse(options.data);
|
|
187
|
+
const result = await applyFix(options.file, item);
|
|
188
|
+
if (result.success) {
|
|
189
|
+
console.log(chalk.green(`✓ Fix applied to ${options.file}`));
|
|
190
|
+
if (result.backup_path) {
|
|
191
|
+
console.log(chalk.dim(` Backup created at ${result.backup_path}`));
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
console.log(chalk.red(`❌ Failed to apply fix: ${result.error}`));
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
} catch (e) {
|
|
198
|
+
console.log(chalk.red(`Error: ${e.message}`));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
153
203
|
|
|
154
204
|
|
|
155
205
|
/**
|
|
@@ -188,7 +238,7 @@ async function generateTest(filePath, methodName, testType, auditMode = false, j
|
|
|
188
238
|
process.exit(1);
|
|
189
239
|
}
|
|
190
240
|
|
|
191
|
-
const {
|
|
241
|
+
const { runAudit, generateTest: nativeGenerateTest } = require('../index');
|
|
192
242
|
|
|
193
243
|
const action = auditMode ? 'Auditing' : 'Generating test for';
|
|
194
244
|
const startMessage = `🔍 ${action} ${methodName} in ${filePath}...`;
|
|
@@ -201,31 +251,45 @@ async function generateTest(filePath, methodName, testType, auditMode = false, j
|
|
|
201
251
|
} else {
|
|
202
252
|
console.log(chalk.blue(startMessage));
|
|
203
253
|
if (testType) {
|
|
204
|
-
console.log(chalk.cyan(
|
|
254
|
+
console.log(chalk.cyan(`Type hint: ${testType}`));
|
|
205
255
|
}
|
|
206
256
|
}
|
|
207
257
|
|
|
208
258
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
reporter.update(msg, percent);
|
|
221
|
-
} else {
|
|
222
|
-
// Simple console progress for CLI mode
|
|
223
|
-
if (percent % 10 === 0 || percent === 100) {
|
|
224
|
-
console.log(chalk.cyan(`[${percent}%] ${msg}`));
|
|
225
|
-
}
|
|
259
|
+
let resultJson;
|
|
260
|
+
|
|
261
|
+
const callback = (msg, percent) => {
|
|
262
|
+
if (jsonSession) {
|
|
263
|
+
// Emit progress events in JSON mode
|
|
264
|
+
const reporter = new (require('../lib/jsonSession').JsonProgressReporter)();
|
|
265
|
+
reporter.update(msg, percent);
|
|
266
|
+
} else {
|
|
267
|
+
// Simple console progress for CLI mode
|
|
268
|
+
if (percent % 10 === 0 || percent === 100) {
|
|
269
|
+
console.log(chalk.cyan(`[${percent}%] ${msg}`));
|
|
226
270
|
}
|
|
227
271
|
}
|
|
228
|
-
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
if (auditMode) {
|
|
275
|
+
resultJson = runAudit(
|
|
276
|
+
absolutePath,
|
|
277
|
+
methodName,
|
|
278
|
+
null, // class_name
|
|
279
|
+
testType || null,
|
|
280
|
+
JSON.stringify(config),
|
|
281
|
+
callback
|
|
282
|
+
);
|
|
283
|
+
} else {
|
|
284
|
+
resultJson = nativeGenerateTest(
|
|
285
|
+
absolutePath,
|
|
286
|
+
methodName,
|
|
287
|
+
null, // class_name
|
|
288
|
+
testType || null,
|
|
289
|
+
JSON.stringify(config),
|
|
290
|
+
callback
|
|
291
|
+
);
|
|
292
|
+
}
|
|
229
293
|
|
|
230
294
|
if (auditMode) {
|
|
231
295
|
// In audit mode, display the results
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/index.d.ts
CHANGED
|
@@ -3,10 +3,44 @@
|
|
|
3
3
|
|
|
4
4
|
/* auto-generated by NAPI-RS */
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Analyzes a JavaScript/TypeScript file and returns its structural outline as a JSON string.
|
|
8
|
+
* Contains information about classes and methods found in the file.
|
|
9
|
+
*/
|
|
6
10
|
export declare function getFileOutline(filePath: string): string
|
|
11
|
+
/**
|
|
12
|
+
* Gathers project-wide metadata from package.json and related files.
|
|
13
|
+
* Returns a JSON string containing dependencies, frameworks, and project type.
|
|
14
|
+
*/
|
|
7
15
|
export declare function getProjectMetadata(projectRoot: string): string
|
|
16
|
+
/**
|
|
17
|
+
* Searches the project for all locations where a specific method is called.
|
|
18
|
+
* Uses ripgrep under the hood for high performance.
|
|
19
|
+
*/
|
|
8
20
|
export declare function findCallSites(projectRoot: string, methodName: string): string
|
|
21
|
+
/** Pings the TNG API to verify connectivity and API key validity. */
|
|
9
22
|
export declare function ping(baseUrl: string, apiKey?: string | undefined | null): string
|
|
23
|
+
/** Submits a test generation job to the API and returns the numeric job ID. */
|
|
10
24
|
export declare function submitJob(baseUrl: string, apiKey: string, payloadJson: string): number
|
|
25
|
+
/** Fetches usage statistics for the authenticated user from the API. */
|
|
11
26
|
export declare function getUserStats(baseUrl: string, apiKey: string): string
|
|
12
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Orchestrates the code audit process.
|
|
29
|
+
* Analyzes the source, builds context, and streams results via the provided callback.
|
|
30
|
+
*/
|
|
31
|
+
export declare function runAudit(filePath: string, methodName: string, className: string | undefined | null, testType: string | undefined | null, configJson: string, callback: (...args: any[]) => any): string
|
|
32
|
+
/**
|
|
33
|
+
* Applies a single edit operation to a file.
|
|
34
|
+
* Uses backup and atomic write for safety.
|
|
35
|
+
*/
|
|
36
|
+
export declare function applyEdit(filePath: string, search: string, replace: string, lineHint?: number | undefined | null): string
|
|
37
|
+
/**
|
|
38
|
+
* Applies multiple edit operations atomically.
|
|
39
|
+
* All succeed or all fail (with rollback).
|
|
40
|
+
*/
|
|
41
|
+
export declare function applyEditsAtomic(operationsJson: string): string
|
|
42
|
+
/**
|
|
43
|
+
* Orchestrates the test generation process.
|
|
44
|
+
* Analyzes code, submits a job, and polls for the final generated test.
|
|
45
|
+
*/
|
|
46
|
+
export declare function generateTest(filePath: string, methodName: string, className: string | undefined | null, testType: string | undefined | null, configJson: string, callback: (...args: any[]) => any): string
|
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,
|
|
313
|
+
const { getFileOutline, getProjectMetadata, findCallSites, ping, submitJob, getUserStats, runAudit, applyEdit, applyEditsAtomic, generateTest } = nativeBinding
|
|
314
314
|
|
|
315
315
|
module.exports.getFileOutline = getFileOutline
|
|
316
316
|
module.exports.getProjectMetadata = getProjectMetadata
|
|
@@ -318,4 +318,7 @@ module.exports.findCallSites = findCallSites
|
|
|
318
318
|
module.exports.ping = ping
|
|
319
319
|
module.exports.submitJob = submitJob
|
|
320
320
|
module.exports.getUserStats = getUserStats
|
|
321
|
-
module.exports.
|
|
321
|
+
module.exports.runAudit = runAudit
|
|
322
|
+
module.exports.applyEdit = applyEdit
|
|
323
|
+
module.exports.applyEditsAtomic = applyEditsAtomic
|
|
324
|
+
module.exports.generateTest = generateTest
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const { parentPort, workerData } = require('worker_threads');
|
|
2
|
+
const { runAudit } = require('../index');
|
|
3
|
+
const { loadConfig } = require('./config');
|
|
4
|
+
|
|
5
|
+
const { filePath, methodName, className, testType } = workerData;
|
|
6
|
+
const config = loadConfig();
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const resultJson = runAudit(
|
|
10
|
+
filePath,
|
|
11
|
+
methodName,
|
|
12
|
+
className,
|
|
13
|
+
testType,
|
|
14
|
+
JSON.stringify(config),
|
|
15
|
+
(msg) => {
|
|
16
|
+
if (parentPort) {
|
|
17
|
+
parentPort.postMessage({ type: 'item', data: msg });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
if (parentPort) {
|
|
22
|
+
parentPort.postMessage({ type: 'result', data: resultJson });
|
|
23
|
+
}
|
|
24
|
+
} catch (e) {
|
|
25
|
+
if (parentPort) {
|
|
26
|
+
parentPort.postMessage({ type: 'error', data: e.message });
|
|
27
|
+
}
|
|
28
|
+
}
|
package/lib/config.js
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const { applyEdit } = require('../index');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parses a SEARCH_AND_REPLACE block from a string
|
|
5
|
+
* @param {string} codeChange
|
|
6
|
+
* @returns {{search: string, replace: string} | null}
|
|
7
|
+
*/
|
|
8
|
+
function parseSearchReplace(codeChange) {
|
|
9
|
+
if (!codeChange) return null;
|
|
10
|
+
|
|
11
|
+
// Try to find SEARCH: and REPLACE: blocks
|
|
12
|
+
const searchIdx = codeChange.indexOf('SEARCH:');
|
|
13
|
+
const replaceIdx = codeChange.indexOf('REPLACE:');
|
|
14
|
+
|
|
15
|
+
if (searchIdx !== -1 && replaceIdx !== -1 && searchIdx < replaceIdx) {
|
|
16
|
+
const search = codeChange.substring(searchIdx + 7, replaceIdx).trim();
|
|
17
|
+
const replace = codeChange.substring(replaceIdx + 8).trim();
|
|
18
|
+
return { search, replace };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Appies a fix to a file
|
|
26
|
+
* @param {string} filePath
|
|
27
|
+
* @param {object} item
|
|
28
|
+
* @returns {object} result
|
|
29
|
+
*/
|
|
30
|
+
async function applyFix(filePath, item) {
|
|
31
|
+
try {
|
|
32
|
+
if (!item) {
|
|
33
|
+
return { success: false, error: 'Item is null or undefined' };
|
|
34
|
+
}
|
|
35
|
+
const codeChange = item.fix_code_change || (item.fix && item.fix.how && item.fix.how.code_change);
|
|
36
|
+
if (!codeChange) {
|
|
37
|
+
return { success: false, error: 'No code change found in fix data' };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const parsed = parseSearchReplace(codeChange);
|
|
41
|
+
if (!parsed) {
|
|
42
|
+
return { success: false, error: 'Failed to parse SEARCH_AND_REPLACE block' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const resultJson = applyEdit(
|
|
46
|
+
filePath,
|
|
47
|
+
parsed.search,
|
|
48
|
+
parsed.replace,
|
|
49
|
+
typeof item.line_number === 'number' && item.line_number > 0 ? item.line_number : null
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const result = JSON.parse(resultJson);
|
|
53
|
+
return result;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return { success: false, error: e.message };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { applyFix, parseSearchReplace };
|