@empiricalrun/test-gen 0.4.2 → 0.6.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/CHANGELOG.md +23 -0
- package/dist/bin/ai/index.d.ts +4 -0
- package/dist/bin/ai/index.d.ts.map +1 -0
- package/dist/bin/ai/index.js +29 -0
- package/dist/bin/ai/prompts/provider/index.d.ts +3 -0
- package/dist/bin/ai/prompts/provider/index.d.ts.map +1 -0
- package/dist/bin/ai/prompts/provider/index.js +16 -0
- package/dist/bin/ai/trace/index.d.ts +36 -0
- package/dist/bin/ai/trace/index.d.ts.map +1 -0
- package/dist/bin/ai/trace/index.js +64 -0
- package/dist/bin/index.js +102 -202
- package/dist/bin/logger/index.d.ts +2 -7
- package/dist/bin/logger/index.d.ts.map +1 -1
- package/dist/bin/logger/index.js +3 -0
- package/dist/bin/reporter/ci.d.ts.map +1 -1
- package/dist/bin/reporter/ci.js +5 -3
- package/dist/bin/scenarios/index.d.ts +1 -1
- package/dist/bin/scenarios/index.d.ts.map +1 -1
- package/dist/bin/scenarios/index.js +41 -10
- package/dist/bin/session/index.d.ts +6 -0
- package/dist/bin/session/index.d.ts.map +1 -0
- package/dist/bin/session/index.js +16 -0
- package/dist/bin/types/index.d.ts.map +1 -1
- package/dist/bin/utils/fs/index.d.ts +5 -0
- package/dist/bin/utils/fs/index.d.ts.map +1 -0
- package/dist/bin/utils/fs/index.js +46 -0
- package/dist/bin/utils/platform/web/index.d.ts +6 -0
- package/dist/bin/utils/platform/web/index.d.ts.map +1 -0
- package/dist/bin/utils/platform/web/index.js +79 -0
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @empiricalrun/test-gen
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3e0e6d1: feat: add support for json string support for adding / updating tests using test-gen
|
|
8
|
+
|
|
9
|
+
## 0.5.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fb09f23: fix: build action in gh
|
|
14
|
+
|
|
15
|
+
## 0.5.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- c09db7e: feat: add observability
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- 77dec1d: feat: add support for editing a scenario
|
|
24
|
+
- 453c8f6: fix: ts feedbacks getting applied to other scenarios
|
|
25
|
+
|
|
3
26
|
## 0.4.2
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/ai/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,OAAO,UAAU,MAAM,SAAS,CAAC;AAEjC,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,EAC9D,KAAK,CAAC,EAAE,UAAU,GACjB,OAAO,CAAC,MAAM,CAAC,CAoBjB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getLLMResult = void 0;
|
|
7
|
+
const openai_1 = __importDefault(require("openai"));
|
|
8
|
+
async function getLLMResult(messages, trace) {
|
|
9
|
+
const openai = new openai_1.default();
|
|
10
|
+
const model = "gpt-4o";
|
|
11
|
+
const parameters = {
|
|
12
|
+
temperature: 0.5,
|
|
13
|
+
};
|
|
14
|
+
const generation = trace?.startGeneration({
|
|
15
|
+
name: "get-llm-result",
|
|
16
|
+
model,
|
|
17
|
+
parameters,
|
|
18
|
+
input: messages,
|
|
19
|
+
});
|
|
20
|
+
const completion = await openai.chat.completions.create({
|
|
21
|
+
messages,
|
|
22
|
+
model,
|
|
23
|
+
...parameters,
|
|
24
|
+
});
|
|
25
|
+
const output = completion.choices[0]?.message.content || "";
|
|
26
|
+
generation?.end({ output });
|
|
27
|
+
return output;
|
|
28
|
+
}
|
|
29
|
+
exports.getLLMResult = getLLMResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/bin/ai/prompts/provider/index.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAS5B,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,GAAG,GACR,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,CAAC,CAI/D"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPrompt = void 0;
|
|
4
|
+
const langfuse_1 = require("langfuse");
|
|
5
|
+
const langfuse = new langfuse_1.Langfuse({
|
|
6
|
+
secretKey: "sk-lf-30eee545-9c2c-4646-bfd9-8f38acf2fa8f",
|
|
7
|
+
publicKey: "pk-lf-c5c7cf23-de76-4098-ad81-594bce7cf330",
|
|
8
|
+
baseUrl: "https://us.cloud.langfuse.com",
|
|
9
|
+
flushAt: 1,
|
|
10
|
+
});
|
|
11
|
+
async function getPrompt(name, vars) {
|
|
12
|
+
const prompt = await langfuse.getPrompt(name, undefined, { type: "chat" });
|
|
13
|
+
const compiledPrompt = prompt.compile(vars);
|
|
14
|
+
return compiledPrompt;
|
|
15
|
+
}
|
|
16
|
+
exports.getPrompt = getPrompt;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
export default class LLMTracing {
|
|
3
|
+
private trace;
|
|
4
|
+
constructor({ name }?: {
|
|
5
|
+
name?: string;
|
|
6
|
+
});
|
|
7
|
+
get id(): string;
|
|
8
|
+
event({ name, input, metadata, output, }: {
|
|
9
|
+
name: string;
|
|
10
|
+
input?: any;
|
|
11
|
+
metadata?: any;
|
|
12
|
+
output?: any;
|
|
13
|
+
}): void;
|
|
14
|
+
update({ input, output, metadata, }: {
|
|
15
|
+
input?: any;
|
|
16
|
+
output?: any;
|
|
17
|
+
metadata?: any;
|
|
18
|
+
}): void;
|
|
19
|
+
startSpan(name: string, input?: Record<string, any>, metadata?: Record<string, any>): {
|
|
20
|
+
end: ({ output }: {
|
|
21
|
+
output: Record<string, any>;
|
|
22
|
+
}) => void;
|
|
23
|
+
};
|
|
24
|
+
startGeneration({ name, model, parameters, input, }: {
|
|
25
|
+
name: string;
|
|
26
|
+
model: string;
|
|
27
|
+
parameters: Record<string, any>;
|
|
28
|
+
input: any;
|
|
29
|
+
}): {
|
|
30
|
+
end: ({ output, usage, }: {
|
|
31
|
+
output: string;
|
|
32
|
+
usage?: OpenAI.Completions.CompletionUsage | undefined;
|
|
33
|
+
}) => void;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/bin/ai/trace/index.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAe5B,MAAM,CAAC,OAAO,OAAO,UAAU;IAC7B,OAAO,CAAC,KAAK,CAAsB;gBACvB,EAAE,IAAuB,EAAE,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO;IAS/D,IAAI,EAAE,WAEL;IAED,KAAK,CAAC,EACJ,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,MAAM,GACP,EAAE;QACD,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,GAAG,CAAC;QACZ,QAAQ,CAAC,EAAE,GAAG,CAAC;QACf,MAAM,CAAC,EAAE,GAAG,CAAC;KACd;IAID,MAAM,CAAC,EACL,KAAU,EACV,MAAW,EACX,QAAa,GACd,EAAE;QACD,KAAK,CAAC,EAAE,GAAG,CAAC;QACZ,MAAM,CAAC,EAAE,GAAG,CAAC;QACb,QAAQ,CAAC,EAAE,GAAG,CAAC;KAChB;IAID,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAC/B,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM;;oBAIJ,OAAO,MAAM,EAAE,GAAG,CAAC;;;IAMnD,eAAe,CAAC,EACd,IAAS,EACT,KAAU,EACV,UAAe,EACf,KAAU,GACX,EAAE;QACD,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,KAAK,EAAE,GAAG,CAAC;KACZ;;oBAYa,MAAM;;cAEZ,IAAI;;CAcb"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const langfuse_1 = require("langfuse");
|
|
4
|
+
const session_1 = require("../../session");
|
|
5
|
+
const langfuse = new langfuse_1.Langfuse({
|
|
6
|
+
secretKey: "sk-lf-30eee545-9c2c-4646-bfd9-8f38acf2fa8f",
|
|
7
|
+
publicKey: "pk-lf-c5c7cf23-de76-4098-ad81-594bce7cf330",
|
|
8
|
+
baseUrl: "https://us.cloud.langfuse.com",
|
|
9
|
+
flushAt: 1,
|
|
10
|
+
});
|
|
11
|
+
process.on("exit", () => langfuse.flush());
|
|
12
|
+
process.on("SIGINT", () => langfuse.flush());
|
|
13
|
+
process.on("SIGTERM", async () => langfuse.flush());
|
|
14
|
+
class LLMTracing {
|
|
15
|
+
trace;
|
|
16
|
+
constructor({ name = "test-generator" } = {}) {
|
|
17
|
+
const sessionDetails = (0, session_1.getSessionDetails)();
|
|
18
|
+
this.trace = langfuse.trace({
|
|
19
|
+
name,
|
|
20
|
+
sessionId: sessionDetails.id,
|
|
21
|
+
release: sessionDetails.version,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
get id() {
|
|
25
|
+
return this.trace.traceId;
|
|
26
|
+
}
|
|
27
|
+
event({ name, input, metadata, output, }) {
|
|
28
|
+
this.trace.event({ name, input, metadata, output });
|
|
29
|
+
}
|
|
30
|
+
update({ input = {}, output = {}, metadata = {}, }) {
|
|
31
|
+
this.trace.update({ input, output, metadata });
|
|
32
|
+
}
|
|
33
|
+
startSpan(name, input = {}, metadata = {}) {
|
|
34
|
+
const span = this.trace.span({ name, metadata, input });
|
|
35
|
+
return {
|
|
36
|
+
end: ({ output }) => {
|
|
37
|
+
span.end({ output });
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
startGeneration({ name = "", model = "", parameters = {}, input = {}, }) {
|
|
42
|
+
const generation = this.trace.generation({
|
|
43
|
+
name,
|
|
44
|
+
model,
|
|
45
|
+
modelParameters: parameters,
|
|
46
|
+
input,
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
end: ({ output, usage, }) => {
|
|
50
|
+
generation.end({
|
|
51
|
+
output,
|
|
52
|
+
usage: usage
|
|
53
|
+
? {
|
|
54
|
+
promptTokens: usage.prompt_tokens,
|
|
55
|
+
completionTokens: usage.completion_tokens,
|
|
56
|
+
totalTokens: usage.total_tokens,
|
|
57
|
+
}
|
|
58
|
+
: undefined,
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.default = LLMTracing;
|
package/dist/bin/index.js
CHANGED
|
@@ -4,252 +4,152 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
-
const openai_1 = __importDefault(require("openai"));
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const typescript_1 = __importDefault(require("typescript"));
|
|
11
|
-
const prettier_1 = __importDefault(require("prettier"));
|
|
12
|
-
const eslint_1 = require("eslint");
|
|
13
7
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const ai_1 = require("./ai");
|
|
10
|
+
const provider_1 = require("./ai/prompts/provider");
|
|
11
|
+
const trace_1 = __importDefault(require("./ai/trace"));
|
|
14
12
|
const logger_1 = require("./logger");
|
|
15
|
-
const scenarios_1 = require("./scenarios");
|
|
16
13
|
const ci_1 = require("./reporter/ci");
|
|
14
|
+
const scenarios_1 = require("./scenarios");
|
|
15
|
+
const fs_1 = require("./utils/fs");
|
|
16
|
+
const web_1 = require("./utils/platform/web");
|
|
17
17
|
dotenv_1.default.config({
|
|
18
18
|
path: [".env.local", ".env"],
|
|
19
19
|
});
|
|
20
20
|
const logger = new logger_1.CustomLogger();
|
|
21
|
-
async function
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
for (const item of items) {
|
|
25
|
-
const fullPath = path_1.default.join(dir, item);
|
|
26
|
-
const stat = await fs_extra_1.default.stat(fullPath);
|
|
27
|
-
if (stat.isDirectory()) {
|
|
28
|
-
files = files.concat(await readFilesInDirectory(fullPath));
|
|
29
|
-
}
|
|
30
|
-
else if (stat.isFile()) {
|
|
31
|
-
const content = await fs_extra_1.default.readFile(fullPath, "utf-8");
|
|
32
|
-
files.push({ filePath: fullPath, content });
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return files;
|
|
36
|
-
}
|
|
37
|
-
function mergeFilesToPrompt(files = []) {
|
|
38
|
-
let prompt = "";
|
|
39
|
-
files.forEach((file) => {
|
|
40
|
-
prompt += `File Path: ${file.filePath}\n`;
|
|
41
|
-
prompt += `File:\n`;
|
|
42
|
-
prompt += `${file.content}\n\n ------ \n\n`;
|
|
43
|
-
});
|
|
44
|
-
return prompt;
|
|
45
|
-
}
|
|
46
|
-
async function generatePromptFromDirectory(dir = "") {
|
|
47
|
-
try {
|
|
48
|
-
const files = await readFilesInDirectory(dir);
|
|
49
|
-
const prompt = mergeFilesToPrompt(files);
|
|
50
|
-
return prompt;
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
console.error("Error reading directory:", error);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
async function getLLMResult(instruction) {
|
|
57
|
-
const openai = new openai_1.default();
|
|
58
|
-
const completion = await openai.chat.completions.create({
|
|
59
|
-
messages: [
|
|
60
|
-
{
|
|
61
|
-
role: "user",
|
|
62
|
-
content: [
|
|
63
|
-
{
|
|
64
|
-
type: "text",
|
|
65
|
-
text: instruction,
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
model: "gpt-4o",
|
|
71
|
-
});
|
|
72
|
-
const response = completion.choices[0]?.message.content || "";
|
|
73
|
-
return response;
|
|
74
|
-
}
|
|
75
|
-
function validateTypescript(filePath) {
|
|
76
|
-
// Create a compiler host to read files
|
|
77
|
-
const compilerHost = typescript_1.default.createCompilerHost({});
|
|
78
|
-
compilerHost.readFile = (file) => fs_extra_1.default.readFileSync(file, "utf8");
|
|
79
|
-
// Create a program with a single source file
|
|
80
|
-
const program = typescript_1.default.createProgram([filePath], {}, compilerHost);
|
|
81
|
-
// Get the source file
|
|
82
|
-
const sourceFile = program.getSourceFile(filePath);
|
|
83
|
-
// Get and report any syntactic errors
|
|
84
|
-
const errors = [];
|
|
85
|
-
const syntacticDiagnostics = program.getSyntacticDiagnostics(sourceFile);
|
|
86
|
-
if (syntacticDiagnostics.length > 0) {
|
|
87
|
-
syntacticDiagnostics.forEach((diagnostic) => {
|
|
88
|
-
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
89
|
-
const message = typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
90
|
-
logger.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
|
|
91
|
-
if (typeof diagnostic.messageText === "string") {
|
|
92
|
-
errors.push(diagnostic.messageText);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
// Get and report any semantic errors
|
|
97
|
-
const semanticDiagnostics = program.getSemanticDiagnostics(sourceFile);
|
|
98
|
-
if (semanticDiagnostics.length > 0) {
|
|
99
|
-
semanticDiagnostics.forEach((diagnostic) => {
|
|
100
|
-
errors.push(diagnostic.messageText.toString());
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
if (!errors.length) {
|
|
104
|
-
logger.success("Found no type issues!");
|
|
105
|
-
}
|
|
106
|
-
return errors;
|
|
107
|
-
}
|
|
108
|
-
async function formatCode(filePath) {
|
|
109
|
-
const fileContent = fs_extra_1.default.readFileSync(filePath, "utf8");
|
|
110
|
-
const prettierConfig = {};
|
|
111
|
-
const formattedContent = await prettier_1.default.format(fileContent, {
|
|
112
|
-
...prettierConfig,
|
|
113
|
-
filepath: filePath,
|
|
114
|
-
});
|
|
115
|
-
fs_extra_1.default.writeFileSync(filePath, formattedContent);
|
|
116
|
-
logger.success("File formatted successfully!");
|
|
117
|
-
}
|
|
118
|
-
async function stripAndPrependImports(content) {
|
|
119
|
-
const imports = content.match(/import.* from.*;/g);
|
|
120
|
-
const strippedContent = content.replace(/import.* from.*;/g, "");
|
|
121
|
-
const prependContent = (imports?.join("\n") || "") + "\n\n";
|
|
122
|
-
return [prependContent, strippedContent];
|
|
123
|
-
}
|
|
124
|
-
async function lintErrors(filePath) {
|
|
125
|
-
const eslint = new eslint_1.ESLint({
|
|
126
|
-
fix: true,
|
|
127
|
-
useEslintrc: true,
|
|
128
|
-
});
|
|
129
|
-
const [result] = await eslint.lintFiles(filePath);
|
|
130
|
-
fs_extra_1.default.writeFileSync(filePath, result?.output || "");
|
|
131
|
-
}
|
|
132
|
-
async function generateTest(scenarios, file) {
|
|
133
|
-
const codePrompt = await generatePromptFromDirectory("./tests");
|
|
134
|
-
const pomPrompt = await generatePromptFromDirectory("./pages");
|
|
21
|
+
async function generateTest(scenarios, file, isUpdate) {
|
|
22
|
+
const codePrompt = await (0, fs_1.generatePromptFromDirectory)("./tests");
|
|
23
|
+
const pomPrompt = await (0, fs_1.generatePromptFromDirectory)("./pages");
|
|
135
24
|
const testFileContent = fs_extra_1.default.readFileSync(file, "utf-8");
|
|
136
25
|
const generatedScenarios = [];
|
|
137
26
|
for (const i in scenarios) {
|
|
138
|
-
|
|
27
|
+
logger.logEmptyLine();
|
|
28
|
+
const trace = new trace_1.default({ name: "generate-test" });
|
|
29
|
+
trace.event({
|
|
30
|
+
name: "collate-files-as-text",
|
|
31
|
+
output: {
|
|
32
|
+
codePrompt,
|
|
33
|
+
pomPrompt,
|
|
34
|
+
testFileContent,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
139
37
|
const scenario = scenarios[i];
|
|
38
|
+
trace.update({ input: { scenario } });
|
|
140
39
|
logger.log("Generating test for scenario:", scenario?.name);
|
|
141
|
-
|
|
142
|
-
if (testFileContent.includes(`test("${scenario?.name}"`)) {
|
|
40
|
+
if (!isUpdate && testFileContent.includes(`test("${scenario?.name}"`)) {
|
|
143
41
|
logger.success("Test already exists for this scenario");
|
|
42
|
+
trace.event({ name: "test-already-exists" });
|
|
144
43
|
continue;
|
|
145
44
|
}
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
${pomPrompt}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
Following is the test scenario for which you need to write test:
|
|
162
|
-
name: ${scenario?.name}
|
|
163
|
-
Steps:
|
|
164
|
-
${scenario?.steps?.join("\n")}
|
|
165
|
-
|
|
166
|
-
Assert:
|
|
167
|
-
${scenario?.assert}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
The code needs to be written inside ${file} file. Write the tests accordingly.
|
|
171
|
-
|
|
172
|
-
Follow these guidelines before responding with output
|
|
173
|
-
- Ensure there are no type issues in the code generated
|
|
174
|
-
- For the given spec file respond with only the test case for the given scenario
|
|
175
|
-
- Donot respond with markdown syntax or backticks
|
|
176
|
-
- Respond only with the code
|
|
177
|
-
- Donot repeat steps which are already mentioned in the "test.beforeEach" block
|
|
178
|
-
- Add import statements at the beginning of the output.
|
|
179
|
-
`;
|
|
180
|
-
let response = await getLLMResult(instruction);
|
|
45
|
+
const promptSpan = trace.startSpan(isUpdate ? "update-scenario-prompt" : "add-scenario-prompt");
|
|
46
|
+
const promptName = isUpdate ? "update-scenario" : "add-scenario";
|
|
47
|
+
const instruction = await (0, provider_1.getPrompt)(promptName, {
|
|
48
|
+
testFiles: codePrompt,
|
|
49
|
+
pageFiles: pomPrompt,
|
|
50
|
+
scenarioName: scenario.name,
|
|
51
|
+
scenarioSteps: scenario.steps.join("\n"),
|
|
52
|
+
assert: scenario.assert,
|
|
53
|
+
scenarioFile: file,
|
|
54
|
+
});
|
|
55
|
+
promptSpan.end({ output: { instruction } });
|
|
56
|
+
let response = await (0, ai_1.getLLMResult)(instruction, trace);
|
|
181
57
|
logger.success("Test generated successfully!");
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
58
|
+
const readWriteFileSpan = trace.startSpan("write-to-file");
|
|
59
|
+
let contents = fs_extra_1.default.readFileSync(file, "utf-8");
|
|
60
|
+
const [prependContent, strippedContent] = await (0, web_1.stripAndPrependImports)(response);
|
|
61
|
+
let updatedContent = prependContent + contents + `\n\n${strippedContent}`;
|
|
62
|
+
if (isUpdate) {
|
|
63
|
+
const testBlock = (0, web_1.getTypescriptTestBlock)(scenario?.name, contents);
|
|
64
|
+
contents = contents.replace(testBlock[0], `\n\n${strippedContent}`);
|
|
65
|
+
updatedContent = prependContent + contents;
|
|
66
|
+
}
|
|
67
|
+
await fs_extra_1.default.writeFile(file, updatedContent, "utf-8");
|
|
68
|
+
readWriteFileSpan.end({ output: { updatedContent } });
|
|
185
69
|
logger.log("Linting generated code...");
|
|
186
|
-
|
|
70
|
+
trace.event({ name: "lint-file" });
|
|
71
|
+
await (0, web_1.lintErrors)(file);
|
|
72
|
+
const validateTypesSpan = trace.startSpan("detect-type-errors-in-file");
|
|
187
73
|
logger.log("Validating types...");
|
|
188
|
-
let errors = validateTypescript(file);
|
|
74
|
+
let errors = (0, web_1.validateTypescript)(file);
|
|
75
|
+
validateTypesSpan.end({ output: { errors } });
|
|
76
|
+
if (!errors.length) {
|
|
77
|
+
logger.success("Found no type issues!");
|
|
78
|
+
}
|
|
189
79
|
const maxIteration = 2;
|
|
190
80
|
let counter = 0;
|
|
191
81
|
while (errors.length > 0) {
|
|
192
82
|
const fileContent = fs_extra_1.default.readFileSync(file, "utf-8");
|
|
193
83
|
counter += 1;
|
|
194
84
|
if (counter > maxIteration) {
|
|
195
|
-
|
|
85
|
+
trace.event({ name: "code-fix-iteration-max-out" });
|
|
86
|
+
logger.error(`Unable to fix typescript errors. Please review ${file} manually and fix the typescript errors. Run the test-gen command again, once errors are fixed. [Trace: ${trace.id}]`);
|
|
196
87
|
break;
|
|
197
88
|
}
|
|
89
|
+
trace.event({ name: "Found errors fixing" });
|
|
198
90
|
logger.warn("Found few errors while validating types:");
|
|
199
|
-
errors.forEach(e => logger.warn(e));
|
|
91
|
+
errors.forEach((e) => logger.warn(e));
|
|
200
92
|
logger.log("Trying to fix above errors...");
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
Here are the typescript errors:
|
|
215
|
-
${errors.join("\n")}
|
|
216
|
-
|
|
217
|
-
Here is the content of the file ${file}:
|
|
218
|
-
|
|
219
|
-
${fileContent}
|
|
220
|
-
|
|
221
|
-
Follow following guidelines before responding with output
|
|
222
|
-
- Ensure there are no type issues in the file ${file}
|
|
223
|
-
- For the given file respond with only the code
|
|
224
|
-
- Donot respond with markdown syntax or backticks
|
|
225
|
-
- Respond only with the code
|
|
226
|
-
- Donot modify anything else apart from the code required to fix typescript error
|
|
227
|
-
`;
|
|
228
|
-
response = await getLLMResult(instruction);
|
|
93
|
+
const promptSpan = trace.startSpan("fix-type-errors-prompt");
|
|
94
|
+
const instruction = await (0, provider_1.getPrompt)("fix-file-errors-ts", {
|
|
95
|
+
testFiles: codePrompt || "",
|
|
96
|
+
pageFiles: pomPrompt || "",
|
|
97
|
+
scenarioFile: file,
|
|
98
|
+
errors: errors,
|
|
99
|
+
fileContent: fileContent,
|
|
100
|
+
scenaioName: scenario.name,
|
|
101
|
+
});
|
|
102
|
+
promptSpan.end({ output: { instruction } });
|
|
103
|
+
response = await (0, ai_1.getLLMResult)(instruction, trace);
|
|
104
|
+
const readWriteFileSpan = trace.startSpan("write-to-file");
|
|
229
105
|
await fs_extra_1.default.writeFile(file, response, "utf-8");
|
|
230
|
-
|
|
231
|
-
|
|
106
|
+
readWriteFileSpan.end({ output: { response } });
|
|
107
|
+
trace.event({ name: "lint-file" });
|
|
108
|
+
await (0, web_1.lintErrors)(file);
|
|
109
|
+
const validateTypesSpan = trace.startSpan("detect-type-errors-in-file");
|
|
110
|
+
errors = (0, web_1.validateTypescript)(file);
|
|
111
|
+
validateTypesSpan.end({ output: { errors } });
|
|
112
|
+
if (!errors.length) {
|
|
113
|
+
logger.success("Found no type issues!");
|
|
114
|
+
}
|
|
232
115
|
}
|
|
233
|
-
|
|
116
|
+
trace.event({ name: "format-file" });
|
|
117
|
+
await (0, web_1.formatCode)(file);
|
|
118
|
+
logger.success("File formatted successfully!", `[Trace: ${trace.id}]`);
|
|
234
119
|
generatedScenarios.push(scenario);
|
|
120
|
+
trace.update({ input: { scenario }, output: { response } });
|
|
235
121
|
}
|
|
236
122
|
return generatedScenarios;
|
|
237
123
|
}
|
|
238
124
|
(async function main() {
|
|
239
|
-
if (process.argv.length
|
|
240
|
-
logger.error("Please provide path to scenarios using command:", "npx @empiricalrun/test-gen <SCENARIOS_FILE_PATH>");
|
|
125
|
+
if (process.argv.length < 3) {
|
|
126
|
+
logger.error("Please provide path to scenarios using command:", "npx @empiricalrun/test-gen <SCENARIOS_FILE_PATH> <SCENARIO_NAME> -u");
|
|
241
127
|
process.exit(1);
|
|
242
128
|
}
|
|
243
129
|
const scenariosPath = process.argv[2];
|
|
244
|
-
const
|
|
130
|
+
const scenario = process.argv[3];
|
|
131
|
+
const isUpdate = process.argv.includes("-u");
|
|
132
|
+
let testGenConfigs = await (0, scenarios_1.generateScenarios)(scenariosPath);
|
|
133
|
+
if (scenario) {
|
|
134
|
+
// filter config
|
|
135
|
+
testGenConfigs = testGenConfigs.filter((t) => t.scenarios.some((s) => s.name === scenario));
|
|
136
|
+
// if no config available then throw error
|
|
137
|
+
if (testGenConfigs.length == 0) {
|
|
138
|
+
logger.error("No scenarios found for the given scenario name", scenario);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
const [testGenConfig] = testGenConfigs;
|
|
142
|
+
// filter scenarios
|
|
143
|
+
testGenConfig.scenarios = testGenConfig.scenarios.filter((s) => s.name === scenario);
|
|
144
|
+
}
|
|
245
145
|
const generatedTestScenarios = [];
|
|
246
146
|
for (const testGenConfig of testGenConfigs) {
|
|
247
|
-
const specPath = testGenConfig
|
|
147
|
+
const { specPath, scenarios } = testGenConfig;
|
|
248
148
|
if (!fs_extra_1.default.existsSync(specPath)) {
|
|
249
149
|
logger.log(`Creating a new spec file: ${specPath}`);
|
|
250
150
|
fs_extra_1.default.createFileSync(specPath);
|
|
251
151
|
}
|
|
252
|
-
const gen = await generateTest(
|
|
152
|
+
const gen = await generateTest(scenarios, specPath, isUpdate);
|
|
253
153
|
generatedTestScenarios.push(...gen);
|
|
254
154
|
}
|
|
255
155
|
await (0, ci_1.reportOnCI)(generatedTestScenarios);
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
export
|
|
2
|
-
log: (message?: string, ...optionalParams: any[]) => void;
|
|
3
|
-
warn: (message?: string, ...optionalParams: any[]) => void;
|
|
4
|
-
error: (message?: string, ...optionalParams: any[]) => void;
|
|
5
|
-
success: (message?: string, ...optionalParams: any[]) => void;
|
|
6
|
-
}
|
|
7
|
-
export declare class CustomLogger implements Logger {
|
|
1
|
+
export declare class CustomLogger {
|
|
8
2
|
log(message?: string, ...optionalParams: any[]): void;
|
|
9
3
|
warn(message?: string, ...optionalParams: any[]): void;
|
|
10
4
|
success(message?: string, ...optionalParams: any[]): void;
|
|
11
5
|
error(message?: string, ...optionalParams: any[]): void;
|
|
6
|
+
logEmptyLine(): void;
|
|
12
7
|
}
|
|
13
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/logger/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/logger/index.ts"],"names":[],"mappings":"AAEA,qBAAa,YAAY;IACvB,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,EAAE;IAI9C,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,EAAE;IAI/C,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,EAAE;IAIlD,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,EAAE;IAIhD,YAAY;CAGb"}
|
package/dist/bin/logger/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../../../src/bin/reporter/ci.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../../../src/bin/reporter/ci.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC,wBAAsB,UAAU,CAAC,SAAS,EAAE,QAAQ,EAAE,uBAerD"}
|
package/dist/bin/reporter/ci.js
CHANGED
|
@@ -8,13 +8,15 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
|
8
8
|
async function reportOnCI(scenarios) {
|
|
9
9
|
if (process.env.CI && process.env.GITHUB_OUTPUT) {
|
|
10
10
|
const testNames = scenarios.map((s) => s.name).join(" ");
|
|
11
|
-
const scenariosOutput = scenarios
|
|
11
|
+
const scenariosOutput = scenarios
|
|
12
|
+
.map((s) => {
|
|
12
13
|
return `**Scenario:** ${s.name} \n\n**Steps:**\n - ${s.steps.join("\n - ")} \n\n**Assert**:\n - ${s.assert}\n\n ----`;
|
|
13
|
-
})
|
|
14
|
+
})
|
|
15
|
+
.join("\n ----- \n");
|
|
14
16
|
const envVars = [
|
|
15
17
|
`summary<<EOF\n${scenariosOutput}\nEOF`,
|
|
16
18
|
`test_names=${testNames}`,
|
|
17
|
-
].join(
|
|
19
|
+
].join("\n");
|
|
18
20
|
await fs_extra_1.default.appendFile(process.env.GITHUB_OUTPUT, envVars);
|
|
19
21
|
}
|
|
20
22
|
return scenarios;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/scenarios/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/scenarios/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAiGpC,iBAAe,iBAAiB,CAC9B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,QAAQ,EAAE,CAAA;CAAE,EAAE,CAAC,CAqBxD;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -4,11 +4,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.generateScenarios = void 0;
|
|
7
|
-
const google_auth_library_1 = require("google-auth-library");
|
|
8
|
-
const slugify_1 = __importDefault(require("slugify"));
|
|
9
7
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const google_auth_library_1 = require("google-auth-library");
|
|
10
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const slugify_1 = __importDefault(require("slugify"));
|
|
11
11
|
const yaml_1 = require("yaml");
|
|
12
|
+
function isValidJSON(str) {
|
|
13
|
+
try {
|
|
14
|
+
JSON.parse(str);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
12
21
|
/**
|
|
13
22
|
* Method to update / add scenarios to the repo.
|
|
14
23
|
* @param path
|
|
@@ -24,24 +33,30 @@ async function generateScenariosUsingGsheet(path) {
|
|
|
24
33
|
const serviceAccountAuth = new google_auth_library_1.JWT({
|
|
25
34
|
email: process.env.GOOGLE_SERVICE_EMAIL,
|
|
26
35
|
key: Buffer.from(process.env.GOOGLE_SERVICE_EMAIL_PRIVATE_KEY, "base64").toString(),
|
|
27
|
-
scopes: [
|
|
36
|
+
scopes: ["https://www.googleapis.com/auth/spreadsheets"],
|
|
28
37
|
});
|
|
29
38
|
const doc = new GoogleSpreadsheet(docId, serviceAccountAuth);
|
|
30
39
|
await doc.loadInfo();
|
|
31
40
|
const sheet = doc.sheetsById[sheetId];
|
|
32
41
|
const rows = await sheet.getRows();
|
|
33
42
|
const map = new Map();
|
|
34
|
-
rows.forEach(r => {
|
|
43
|
+
rows.forEach((r) => {
|
|
35
44
|
// TODO: fix for case insensitive
|
|
36
45
|
const category = r.get("Category");
|
|
37
46
|
const name = r.get("Scenario");
|
|
38
|
-
const steps =
|
|
47
|
+
const steps = r
|
|
48
|
+
.get("Steps")
|
|
49
|
+
.split("\n")
|
|
50
|
+
.map((s) => s.trim())
|
|
51
|
+
.filter((s) => !!s.length);
|
|
39
52
|
const assert = r.get("Assert");
|
|
40
|
-
const specPath = category
|
|
53
|
+
const specPath = category
|
|
54
|
+
? `./tests/${category}.spec.ts`
|
|
55
|
+
: `./tests/${(0, slugify_1.default)(name)}.spec.ts`;
|
|
41
56
|
const scenario = {
|
|
42
57
|
steps,
|
|
43
58
|
name,
|
|
44
|
-
assert
|
|
59
|
+
assert,
|
|
45
60
|
};
|
|
46
61
|
if (!map.get(specPath)) {
|
|
47
62
|
map.set(specPath, [scenario]);
|
|
@@ -56,14 +71,14 @@ async function generateScenariosUsingGsheet(path) {
|
|
|
56
71
|
for (const [specPath, scenarios] of map.entries()) {
|
|
57
72
|
results.push({
|
|
58
73
|
specPath,
|
|
59
|
-
scenarios
|
|
74
|
+
scenarios,
|
|
60
75
|
});
|
|
61
76
|
}
|
|
62
77
|
return results;
|
|
63
78
|
}
|
|
64
79
|
async function generateScenariosUsingYAML(scenariosPath) {
|
|
65
|
-
const file = await fs_extra_1.default.readFile(path_1.default.resolve(process.cwd(), scenariosPath),
|
|
66
|
-
const fileName = scenariosPath.split(
|
|
80
|
+
const file = await fs_extra_1.default.readFile(path_1.default.resolve(process.cwd(), scenariosPath), "utf8");
|
|
81
|
+
const fileName = scenariosPath.split("/").pop()?.split(".")[0];
|
|
67
82
|
const config = (0, yaml_1.parse)(file);
|
|
68
83
|
const fileDir = config.dir || "./tests";
|
|
69
84
|
config.specPath = `${fileDir}/${fileName}.spec.ts`;
|
|
@@ -73,6 +88,22 @@ async function generateScenarios(scenariosPath) {
|
|
|
73
88
|
if (scenariosPath.startsWith("https://docs.google.com/spreadsheets")) {
|
|
74
89
|
return await generateScenariosUsingGsheet(scenariosPath);
|
|
75
90
|
}
|
|
91
|
+
else if (isValidJSON(scenariosPath)) {
|
|
92
|
+
const config = JSON.parse(scenariosPath);
|
|
93
|
+
const specPath = `./tests/${config.group || "index"}.spec.ts`;
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
specPath,
|
|
97
|
+
scenarios: [
|
|
98
|
+
{
|
|
99
|
+
name: config.name,
|
|
100
|
+
steps: config.steps,
|
|
101
|
+
assert: config.assert,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
}
|
|
76
107
|
else {
|
|
77
108
|
return await generateScenariosUsingYAML(scenariosPath);
|
|
78
109
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/session/index.ts"],"names":[],"mappings":"AAKA,iBAAS,iBAAiB;;;EAKzB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getSessionDetails = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const package_json_1 = __importDefault(require("./../../../package.json"));
|
|
9
|
+
const sessionId = crypto_1.default.randomUUID();
|
|
10
|
+
function getSessionDetails() {
|
|
11
|
+
return {
|
|
12
|
+
id: sessionId,
|
|
13
|
+
version: package_json_1.default.version,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
exports.getSessionDetails = getSessionDetails;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,QAAQ,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { FileContent } from "../../types";
|
|
2
|
+
export declare function readFilesInDirectory(dir?: string): Promise<FileContent[]>;
|
|
3
|
+
export declare function convertFileContentsToString(files?: FileContent[]): string;
|
|
4
|
+
export declare function generatePromptFromDirectory(dir?: string): Promise<string | undefined>;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/bin/utils/fs/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,wBAAsB,oBAAoB,CAAC,GAAG,GAAE,MAAW,0BAgB1D;AAED,wBAAgB,2BAA2B,CAAC,KAAK,GAAE,WAAW,EAAO,UAQpE;AAED,wBAAsB,2BAA2B,CAAC,GAAG,SAAK,+BAQzD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generatePromptFromDirectory = exports.convertFileContentsToString = exports.readFilesInDirectory = void 0;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function readFilesInDirectory(dir = "") {
|
|
10
|
+
let files = [];
|
|
11
|
+
const items = await fs_extra_1.default.readdir(dir);
|
|
12
|
+
for (const item of items) {
|
|
13
|
+
const fullPath = path_1.default.join(dir, item);
|
|
14
|
+
const stat = await fs_extra_1.default.stat(fullPath);
|
|
15
|
+
if (stat.isDirectory()) {
|
|
16
|
+
files = files.concat(await readFilesInDirectory(fullPath));
|
|
17
|
+
}
|
|
18
|
+
else if (stat.isFile()) {
|
|
19
|
+
const content = await fs_extra_1.default.readFile(fullPath, "utf-8");
|
|
20
|
+
files.push({ filePath: fullPath, content });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return files;
|
|
24
|
+
}
|
|
25
|
+
exports.readFilesInDirectory = readFilesInDirectory;
|
|
26
|
+
function convertFileContentsToString(files = []) {
|
|
27
|
+
let prompt = "";
|
|
28
|
+
files.forEach((file) => {
|
|
29
|
+
prompt += `File Path: ${file.filePath}\n`;
|
|
30
|
+
prompt += `File:\n`;
|
|
31
|
+
prompt += `${file.content}\n\n ------ \n\n`;
|
|
32
|
+
});
|
|
33
|
+
return prompt;
|
|
34
|
+
}
|
|
35
|
+
exports.convertFileContentsToString = convertFileContentsToString;
|
|
36
|
+
async function generatePromptFromDirectory(dir = "") {
|
|
37
|
+
try {
|
|
38
|
+
const files = await readFilesInDirectory(dir);
|
|
39
|
+
const prompt = convertFileContentsToString(files);
|
|
40
|
+
return prompt;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error("Error reading directory:", error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.generatePromptFromDirectory = generatePromptFromDirectory;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function getTypescriptTestBlock(scenarioName: string, content: string): RegExpExecArray | null;
|
|
2
|
+
export declare function validateTypescript(filePath: string): string[];
|
|
3
|
+
export declare function stripAndPrependImports(content: string): Promise<string[]>;
|
|
4
|
+
export declare function lintErrors(filePath: string): Promise<void>;
|
|
5
|
+
export declare function formatCode(filePath: string): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/bin/utils/platform/web/index.ts"],"names":[],"mappings":"AAKA,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,0BAG3E;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAwC7D;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,qBAK3D;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,iBAOhD;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,iBAQhD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.formatCode = exports.lintErrors = exports.stripAndPrependImports = exports.validateTypescript = exports.getTypescriptTestBlock = void 0;
|
|
7
|
+
const eslint_1 = require("eslint");
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const prettier_1 = __importDefault(require("prettier"));
|
|
10
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
11
|
+
function getTypescriptTestBlock(scenarioName, content) {
|
|
12
|
+
const regex = new RegExp(`test\\("${scenarioName}"[^]*?\\}\\);`, "g");
|
|
13
|
+
return regex.exec(content);
|
|
14
|
+
}
|
|
15
|
+
exports.getTypescriptTestBlock = getTypescriptTestBlock;
|
|
16
|
+
function validateTypescript(filePath) {
|
|
17
|
+
// Create a compiler host to read files
|
|
18
|
+
const compilerHost = typescript_1.default.createCompilerHost({});
|
|
19
|
+
compilerHost.readFile = (file) => fs_extra_1.default.readFileSync(file, "utf8");
|
|
20
|
+
// Create a program with a single source file
|
|
21
|
+
const program = typescript_1.default.createProgram([filePath], {}, compilerHost);
|
|
22
|
+
// Get the source file
|
|
23
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
24
|
+
// Get and report any syntactic errors
|
|
25
|
+
const errors = [];
|
|
26
|
+
const syntacticDiagnostics = program.getSyntacticDiagnostics(sourceFile);
|
|
27
|
+
if (syntacticDiagnostics.length > 0) {
|
|
28
|
+
syntacticDiagnostics.forEach((diagnostic) => {
|
|
29
|
+
// const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
|
|
30
|
+
// diagnostic.start,
|
|
31
|
+
// );
|
|
32
|
+
// const message = ts.flattenDiagnosticMessageText(
|
|
33
|
+
// diagnostic.messageText,
|
|
34
|
+
// "\n",
|
|
35
|
+
// );
|
|
36
|
+
// logger.log(
|
|
37
|
+
// `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`,
|
|
38
|
+
// );
|
|
39
|
+
if (typeof diagnostic.messageText === "string") {
|
|
40
|
+
errors.push(diagnostic.messageText);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Get and report any semantic errors
|
|
45
|
+
const semanticDiagnostics = program.getSemanticDiagnostics(sourceFile);
|
|
46
|
+
if (semanticDiagnostics.length > 0) {
|
|
47
|
+
semanticDiagnostics.forEach((diagnostic) => {
|
|
48
|
+
errors.push(diagnostic.messageText.toString());
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return errors;
|
|
52
|
+
}
|
|
53
|
+
exports.validateTypescript = validateTypescript;
|
|
54
|
+
async function stripAndPrependImports(content) {
|
|
55
|
+
const imports = content.match(/import.* from.*;/g);
|
|
56
|
+
const strippedContent = content.replace(/import.* from.*;/g, "");
|
|
57
|
+
const prependContent = (imports?.join("\n") || "") + "\n\n";
|
|
58
|
+
return [prependContent, strippedContent];
|
|
59
|
+
}
|
|
60
|
+
exports.stripAndPrependImports = stripAndPrependImports;
|
|
61
|
+
async function lintErrors(filePath) {
|
|
62
|
+
const eslint = new eslint_1.ESLint({
|
|
63
|
+
fix: true,
|
|
64
|
+
useEslintrc: true,
|
|
65
|
+
});
|
|
66
|
+
const [result] = await eslint.lintFiles(filePath);
|
|
67
|
+
fs_extra_1.default.writeFileSync(filePath, result?.output || "");
|
|
68
|
+
}
|
|
69
|
+
exports.lintErrors = lintErrors;
|
|
70
|
+
async function formatCode(filePath) {
|
|
71
|
+
const fileContent = fs_extra_1.default.readFileSync(filePath, "utf8");
|
|
72
|
+
const prettierConfig = {};
|
|
73
|
+
const formattedContent = await prettier_1.default.format(fileContent, {
|
|
74
|
+
...prettierConfig,
|
|
75
|
+
filepath: filePath,
|
|
76
|
+
});
|
|
77
|
+
fs_extra_1.default.writeFileSync(filePath, formattedContent);
|
|
78
|
+
}
|
|
79
|
+
exports.formatCode = formatCode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empiricalrun/test-gen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -15,11 +15,14 @@
|
|
|
15
15
|
},
|
|
16
16
|
"author": "Empirical Team <hey@empirical.run>",
|
|
17
17
|
"dependencies": {
|
|
18
|
+
"commander": "^12.1.0",
|
|
18
19
|
"dotenv": "^16.4.5",
|
|
19
20
|
"eslint": "^8.57.0",
|
|
20
21
|
"fs-extra": "^11.2.0",
|
|
21
22
|
"google-auth-library": "^9.10.0",
|
|
22
23
|
"google-spreadsheet": "^4.1.2",
|
|
24
|
+
"langfuse": "^3.11.2",
|
|
25
|
+
"langfuse-core": "^3.11.2",
|
|
23
26
|
"openai": "^4.47.2",
|
|
24
27
|
"picocolors": "^1.0.1",
|
|
25
28
|
"prettier": "^3.2.5",
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
},
|
|
33
36
|
"scripts": {
|
|
34
37
|
"dev": "tsc --build --watch",
|
|
35
|
-
"build": "tsc --build",
|
|
38
|
+
"build": "tsc --build && node ./../../tools/static-env-vars.js dist",
|
|
36
39
|
"clean": "tsc --build --clean",
|
|
37
40
|
"lint": "eslint .",
|
|
38
41
|
"test": "vitest run",
|