@empiricalrun/test-gen 0.4.1 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @empiricalrun/test-gen
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - fb09f23: fix: build action in gh
8
+
9
+ ## 0.5.0
10
+
11
+ ### Minor Changes
12
+
13
+ - c09db7e: feat: add observability
14
+
15
+ ### Patch Changes
16
+
17
+ - 77dec1d: feat: add support for editing a scenario
18
+ - 453c8f6: fix: ts feedbacks getting applied to other scenarios
19
+
20
+ ## 0.4.2
21
+
22
+ ### Patch Changes
23
+
24
+ - 2b3a59c: chore: more line breaks in ci report
25
+
3
26
  ## 0.4.1
4
27
 
5
28
  ### Patch Changes
@@ -0,0 +1,4 @@
1
+ import OpenAI from "openai";
2
+ import LLMTracing from "./trace";
3
+ export declare function getLLMResult(messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[], trace?: LLMTracing): Promise<string>;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ import OpenAI from "openai";
2
+ export declare function getPrompt(name: string, vars: any): Promise<OpenAI.Chat.Completions.ChatCompletionMessageParam[]>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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 readFilesInDirectory(dir = "") {
22
- let files = [];
23
- const items = await fs_extra_1.default.readdir(dir);
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
- console.log("\n\n");
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
- //TODO: improve this logic. its buggy
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 instruction = `
147
- You are a software test engineer who is given a task to create test basis a scenario provided to you.
148
- You will be provided with current tests, fixtures and page object models for you to use and write test.
149
- You need to respond with only with the test case in typescript language using playwright.
150
-
151
- Here is the list of current tests and fixtures:
152
-
153
- ${codePrompt}
154
-
155
-
156
- Here is the list of current page object models:
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 contents = fs_extra_1.default.readFileSync(file, "utf-8");
183
- const [prependContent, strippedContent] = await stripAndPrependImports(response);
184
- await fs_extra_1.default.writeFile(file, prependContent + contents + `\n\n${strippedContent}`, "utf-8");
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
- await lintErrors(file);
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
- 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`);
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 instruction = `
202
- You are a software engineer who is given a task to create test basis a scenario provided to you.
203
- You will be provided with current tests, fixtures and page object models for you to use and write test.
204
- There are following typescript errors in ${file} which you need to fix.
205
-
206
- Here is the list of current tests and fixtures:
207
-
208
- ${codePrompt}
209
-
210
- Here is the list of current page object models:
211
-
212
- ${pomPrompt}
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
- await lintErrors(file);
231
- errors = validateTypescript(file);
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
- await formatCode(file);
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 != 3) {
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 testGenConfigs = await (0, scenarios_1.generateScenarios)(scenariosPath);
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.specPath;
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(testGenConfig.scenarios, specPath);
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 interface Logger {
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":"AACA,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC1D,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC3D,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC5D,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;CAC/D;AAED,qBAAa,YAAa,YAAW,MAAM;IACzC,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;CAGjD"}
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"}
@@ -15,5 +15,8 @@ class CustomLogger {
15
15
  error(message, ...optionalParams) {
16
16
  console.log("🚨", (0, picocolors_1.red)(message), ...optionalParams);
17
17
  }
18
+ logEmptyLine() {
19
+ console.log("\n\n");
20
+ }
18
21
  }
19
22
  exports.CustomLogger = CustomLogger;
@@ -1 +1 @@
1
- {"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../../../src/bin/reporter/ci.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGpC,wBAAsB,UAAU,CAAE,SAAS,EAAE,QAAQ,EAAE,uBAatD"}
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"}
@@ -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.map((s) => {
12
- return `**Scenario:** ${s.name} \n**Steps:**\n - ${s.steps.join("\n - ")} \n**Assert**:\n - ${s.assert}\n\n ----`;
13
- }).join("\n ----- \n");
11
+ const scenariosOutput = scenarios
12
+ .map((s) => {
13
+ return `**Scenario:** ${s.name} \n\n**Steps:**\n - ${s.steps.join("\n - ")} \n\n**Assert**:\n - ${s.assert}\n\n ----`;
14
+ })
15
+ .join("\n ----- \n");
14
16
  const envVars = [
15
17
  `summary<<EOF\n${scenariosOutput}\nEOF`,
16
18
  `test_names=${testNames}`,
17
- ].join('\n');
19
+ ].join("\n");
18
20
  await fs_extra_1.default.appendFile(process.env.GITHUB_OUTPUT, envVars);
19
21
  }
20
22
  return scenarios;
@@ -1,4 +1,4 @@
1
- import { Scenario } from '../types';
1
+ import { Scenario } from "../types";
2
2
  declare function generateScenarios(scenariosPath: string): Promise<{
3
3
  specPath: string;
4
4
  scenarios: Scenario[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/scenarios/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAsEpC,iBAAe,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,QAAQ,EAAE,CAAA;CAAC,EAAE,CAAC,CAM7G;AAID,OAAO,EACH,iBAAiB,EACpB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/scenarios/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAiFpC,iBAAe,iBAAiB,CAC9B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,QAAQ,EAAE,CAAA;CAAE,EAAE,CAAC,CAMxD;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
@@ -4,10 +4,10 @@ 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
12
  /**
13
13
  * Method to update / add scenarios to the repo.
@@ -24,24 +24,30 @@ async function generateScenariosUsingGsheet(path) {
24
24
  const serviceAccountAuth = new google_auth_library_1.JWT({
25
25
  email: process.env.GOOGLE_SERVICE_EMAIL,
26
26
  key: Buffer.from(process.env.GOOGLE_SERVICE_EMAIL_PRIVATE_KEY, "base64").toString(),
27
- scopes: ['https://www.googleapis.com/auth/spreadsheets'],
27
+ scopes: ["https://www.googleapis.com/auth/spreadsheets"],
28
28
  });
29
29
  const doc = new GoogleSpreadsheet(docId, serviceAccountAuth);
30
30
  await doc.loadInfo();
31
31
  const sheet = doc.sheetsById[sheetId];
32
32
  const rows = await sheet.getRows();
33
33
  const map = new Map();
34
- rows.forEach(r => {
34
+ rows.forEach((r) => {
35
35
  // TODO: fix for case insensitive
36
36
  const category = r.get("Category");
37
37
  const name = r.get("Scenario");
38
- const steps = (r.get("Steps").split("\n")).map((s) => s.trim()).filter((s) => !!s.length);
38
+ const steps = r
39
+ .get("Steps")
40
+ .split("\n")
41
+ .map((s) => s.trim())
42
+ .filter((s) => !!s.length);
39
43
  const assert = r.get("Assert");
40
- const specPath = category ? `./tests/${category}.spec.ts` : `./tests/${(0, slugify_1.default)(name)}.spec.ts`;
44
+ const specPath = category
45
+ ? `./tests/${category}.spec.ts`
46
+ : `./tests/${(0, slugify_1.default)(name)}.spec.ts`;
41
47
  const scenario = {
42
48
  steps,
43
49
  name,
44
- assert
50
+ assert,
45
51
  };
46
52
  if (!map.get(specPath)) {
47
53
  map.set(specPath, [scenario]);
@@ -56,14 +62,14 @@ async function generateScenariosUsingGsheet(path) {
56
62
  for (const [specPath, scenarios] of map.entries()) {
57
63
  results.push({
58
64
  specPath,
59
- scenarios
65
+ scenarios,
60
66
  });
61
67
  }
62
68
  return results;
63
69
  }
64
70
  async function generateScenariosUsingYAML(scenariosPath) {
65
- const file = await fs_extra_1.default.readFile(path_1.default.resolve(process.cwd(), scenariosPath), 'utf8');
66
- const fileName = scenariosPath.split('/').pop()?.split(".")[0];
71
+ const file = await fs_extra_1.default.readFile(path_1.default.resolve(process.cwd(), scenariosPath), "utf8");
72
+ const fileName = scenariosPath.split("/").pop()?.split(".")[0];
67
73
  const config = (0, yaml_1.parse)(file);
68
74
  const fileDir = config.dir || "./tests";
69
75
  config.specPath = `${fileDir}/${fileName}.spec.ts`;
@@ -0,0 +1,6 @@
1
+ declare function getSessionDetails(): {
2
+ id: `${string}-${string}-${string}-${string}-${string}`;
3
+ version: string;
4
+ };
5
+ export { getSessionDetails };
6
+ //# sourceMappingURL=index.d.ts.map
@@ -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;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAA;AAEH,MAAM,MAAM,aAAa,GAAG;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,QAAQ,EAAE,CAAC;CACzB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAClB,CAAA"}
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.4.1",
3
+ "version": "0.5.1",
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",