@empiricalrun/test-gen 0.25.2 → 0.27.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 +16 -0
- package/dist/actions/index.js +1 -1
- package/dist/agent/browsing/run.d.ts +25 -1
- package/dist/agent/browsing/run.d.ts.map +1 -1
- package/dist/agent/browsing/run.js +38 -12
- package/dist/agent/browsing/utils.d.ts +16 -2
- package/dist/agent/browsing/utils.d.ts.map +1 -1
- package/dist/agent/browsing/utils.js +90 -25
- package/dist/agent/codegen/run.d.ts.map +1 -1
- package/dist/agent/codegen/run.js +1 -0
- package/dist/agent/codegen/update-flow.d.ts +5 -1
- package/dist/agent/codegen/update-flow.d.ts.map +1 -1
- package/dist/agent/codegen/update-flow.js +34 -18
- package/dist/bin/index.js +5 -2
- package/dist/bin/utils/context.js +2 -2
- package/dist/bin/utils/platform/web/index.d.ts +33 -1
- package/dist/bin/utils/platform/web/index.d.ts.map +1 -1
- package/dist/bin/utils/platform/web/index.js +58 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @empiricalrun/test-gen
|
|
2
2
|
|
|
3
|
+
## 0.27.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 88067ed: fix: create test script remain in code after test gen error
|
|
8
|
+
|
|
9
|
+
## 0.26.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 5882a20: feat: add inline master agent support
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 5882a20: fix: improve code agent accuracy with updated prompt
|
|
18
|
+
|
|
3
19
|
## 0.25.2
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/actions/index.js
CHANGED
|
@@ -43,7 +43,7 @@ class PlaywrightActions {
|
|
|
43
43
|
}
|
|
44
44
|
catch (e) {
|
|
45
45
|
logger.log(`action: ${name} \nreason: ${args.reason}`);
|
|
46
|
-
throw Error(`Error executing ${name} action
|
|
46
|
+
throw Error(`Error executing ${name} action: ${e.message}`);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
getActionSchemas() {
|
|
@@ -1,2 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
type GenerateTestsType = {
|
|
2
|
+
/**
|
|
3
|
+
* Path to the test case file being updated or created
|
|
4
|
+
*
|
|
5
|
+
* @type {string}
|
|
6
|
+
*/
|
|
7
|
+
testFilePath: string;
|
|
8
|
+
/**
|
|
9
|
+
* File path being updated for the concerned test case
|
|
10
|
+
*
|
|
11
|
+
* @type {string}
|
|
12
|
+
*/
|
|
13
|
+
filePathToUpdate: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* Function to generate tests using master agent
|
|
18
|
+
* @export
|
|
19
|
+
* @param {GenerateTestsType} {
|
|
20
|
+
* testFilePath,
|
|
21
|
+
* filePathToUpdate,
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
export declare function generateTestsUsingMasterAgent({ testFilePath, filePathToUpdate, }: GenerateTestsType): Promise<void>;
|
|
25
|
+
export {};
|
|
2
26
|
//# sourceMappingURL=run.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/run.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/run.ts"],"names":[],"mappings":"AAiBA,KAAK,iBAAiB,GAAG;IACvB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,6BAA6B,CAAC,EAClD,YAAY,EACZ,gBAAgB,GACjB,EAAE,iBAAiB,iBAqDnB"}
|
|
@@ -3,44 +3,70 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.generateTestsUsingMasterAgent = void 0;
|
|
7
7
|
const detect_port_1 = __importDefault(require("detect-port"));
|
|
8
|
-
const
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
9
|
const utils_1 = require("../../bin/utils");
|
|
10
10
|
const web_1 = require("../../bin/utils/platform/web");
|
|
11
11
|
const server_1 = require("../../file/server");
|
|
12
|
-
const reporter_1 = require("../../reporter");
|
|
13
12
|
const exec_1 = require("../../utils/exec");
|
|
14
13
|
const utils_2 = require("./utils");
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* Function to generate tests using master agent
|
|
17
|
+
* @export
|
|
18
|
+
* @param {GenerateTestsType} {
|
|
19
|
+
* testFilePath,
|
|
20
|
+
* filePathToUpdate,
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
async function generateTestsUsingMasterAgent({ testFilePath, filePathToUpdate, }) {
|
|
24
|
+
// valiate if the file path and file to update are valid
|
|
25
|
+
// also warn users if they are on older version of test-gen
|
|
26
|
+
(0, utils_2.canRunMasterAgent)(testFilePath);
|
|
27
|
+
// detect available http port on the machine
|
|
18
28
|
const port = await (0, detect_port_1.default)(3030);
|
|
29
|
+
// start a file service to handle file updates from agent
|
|
30
|
+
// - also update the file path with updates when agent is done spitting out code
|
|
19
31
|
const fileService = new server_1.FileService({ port });
|
|
20
32
|
await fileService.startFileService();
|
|
21
|
-
fileService.setFilePath(
|
|
33
|
+
fileService.setFilePath(filePathToUpdate);
|
|
22
34
|
// read playwright config from ./playwright.config.ts of source repo
|
|
23
35
|
const playwrightConfig = await (0, utils_2.readPlaywrightConfig)();
|
|
36
|
+
// detect the playwright project name for the given test file and playwright config
|
|
24
37
|
const project = await (0, utils_2.detectProjectName)(testFilePath, playwrightConfig);
|
|
25
|
-
|
|
26
|
-
//
|
|
38
|
+
console.log(`Detected playwright project name: ${project}`);
|
|
39
|
+
// run playwright test which will internally run the master agent
|
|
27
40
|
let command = `npx playwright test ${testFilePath} --retries 0 --project ${project} --timeout 0`;
|
|
28
41
|
if (!process.env.CI) {
|
|
29
42
|
command = command.concat(` --headed`);
|
|
30
43
|
}
|
|
44
|
+
let isError = false;
|
|
31
45
|
try {
|
|
32
46
|
await (0, exec_1.cmd)(command.split(" "), {
|
|
33
47
|
env: {
|
|
34
48
|
APP_PORT: port.toString(),
|
|
35
49
|
PW_TEST_HTML_REPORT_OPEN: "never",
|
|
50
|
+
// pass the test gen token so that the agent has the same configuration as cli
|
|
36
51
|
TEST_GEN_TOKEN: (0, utils_1.getTestConfigCliArg)(),
|
|
37
52
|
},
|
|
38
53
|
});
|
|
39
54
|
}
|
|
40
55
|
catch (e) {
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
console.error(e);
|
|
57
|
+
isError = true;
|
|
58
|
+
}
|
|
59
|
+
if (isError) {
|
|
60
|
+
try {
|
|
61
|
+
const fileContent = await fs_extra_1.default.readFile(filePathToUpdate, "utf-8");
|
|
62
|
+
const updatedContent = (0, web_1.replaceCreateTestWithNewCode)(filePathToUpdate, fileContent, "");
|
|
63
|
+
await fs_extra_1.default.writeFile(filePathToUpdate, updatedContent, "utf-8");
|
|
64
|
+
await (0, web_1.lintErrors)(filePathToUpdate);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
console.error("Failed to remove extra scripts from files post test gen error", e);
|
|
68
|
+
}
|
|
43
69
|
}
|
|
44
70
|
await (0, web_1.removeTestOnly)(testFilePath);
|
|
45
71
|
}
|
|
46
|
-
exports.
|
|
72
|
+
exports.generateTestsUsingMasterAgent = generateTestsUsingMasterAgent;
|
|
@@ -3,9 +3,23 @@ import { PlaywrightTestConfig } from "playwright/test";
|
|
|
3
3
|
import { TestGenConfig } from "../../types";
|
|
4
4
|
export declare function isRegExp(obj: any): obj is RegExp;
|
|
5
5
|
export declare function prepareBrowsingAgentTask(steps: string[]): string;
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Function to prepare test file for master agent to run
|
|
8
|
+
* @param {TestGenConfig} genConfig
|
|
9
|
+
* @return {*} {Promise<string>}
|
|
10
|
+
*/
|
|
11
|
+
export declare function prepareFileForMasterAgent(genConfig: TestGenConfig): Promise<string>;
|
|
7
12
|
export declare function injectPwLocatorGenerator(page: Page): Promise<void>;
|
|
8
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Function to validate if the test file path are valid.
|
|
15
|
+
* @throws if there are any missing dependencies for master agent to run.
|
|
16
|
+
* @param {string} filePath
|
|
17
|
+
*/
|
|
18
|
+
export declare function canRunMasterAgent(filePath: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* function to read playwright config from the source repo
|
|
21
|
+
* @return {*} {Promise<PlaywrightTestConfig>}
|
|
22
|
+
*/
|
|
9
23
|
export declare function readPlaywrightConfig(): Promise<PlaywrightTestConfig>;
|
|
10
24
|
/**
|
|
11
25
|
* detect the project name for the given file in playwright test repo
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAUvD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAUvD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,MAAM,CAKhD;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,UAIvD;AAyFD;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,aAAa,GACvB,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAwCD,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,iBAiBxD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,QA6BjD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAM1E;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,oBAAoB,GACrC,OAAO,CAAC,MAAM,CAAC,CAkDjB;AAED,wBAAsB,sBAAsB,CAAC,EAC3C,YAAiB,EACjB,IAAS,EACT,eAAoB,EACpB,gBAAqB,EACrB,UAAyC,GAC1C,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,8EASA"}
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getPromptForNextAction = exports.detectProjectName = exports.readPlaywrightConfig = exports.
|
|
6
|
+
exports.getPromptForNextAction = exports.detectProjectName = exports.readPlaywrightConfig = exports.canRunMasterAgent = exports.injectPwLocatorGenerator = exports.prepareFileForMasterAgent = exports.prepareBrowsingAgentTask = exports.isRegExp = void 0;
|
|
7
7
|
const llm_1 = require("@empiricalrun/llm");
|
|
8
8
|
const child_process_1 = require("child_process");
|
|
9
9
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
@@ -11,6 +11,7 @@ const minimatch_1 = require("minimatch");
|
|
|
11
11
|
const api_1 = __importDefault(require("tsx/cjs/api"));
|
|
12
12
|
const logger_1 = require("../../bin/logger");
|
|
13
13
|
const web_1 = require("../../bin/utils/platform/web");
|
|
14
|
+
const update_flow_1 = require("../codegen/update-flow");
|
|
14
15
|
function isRegExp(obj) {
|
|
15
16
|
return (obj instanceof RegExp ||
|
|
16
17
|
Object.prototype.toString.call(obj) === "[object RegExp]");
|
|
@@ -22,36 +23,91 @@ function prepareBrowsingAgentTask(steps) {
|
|
|
22
23
|
return task;
|
|
23
24
|
}
|
|
24
25
|
exports.prepareBrowsingAgentTask = prepareBrowsingAgentTask;
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Function to prepare test file for update scenarios for master agent to run
|
|
28
|
+
* @param {TestGenConfig} genConfig
|
|
29
|
+
*/
|
|
30
|
+
async function prepareFileForUpdateScenario(genConfig) {
|
|
31
|
+
const { specPath, testCase } = genConfig;
|
|
32
|
+
const { name, steps } = testCase;
|
|
33
|
+
// update the test case with appropriate location for createTest
|
|
34
|
+
// TODO: reduce the payload for this LLM call. Only provide test file and page files which are used in the test block
|
|
35
|
+
// this will help with faster response
|
|
36
|
+
const [suggestion] = await (0, update_flow_1.updateTest)({
|
|
37
|
+
...testCase,
|
|
38
|
+
steps: [
|
|
39
|
+
...steps,
|
|
40
|
+
`
|
|
41
|
+
- You are given a method with interface await createTest(<task>, <playwright_page_instance>)
|
|
42
|
+
- This method will help you execute given task.
|
|
43
|
+
- Given the task you need to decide the right file path and code block to place this method.
|
|
44
|
+
- The task should contain hints as in where to place this method.
|
|
45
|
+
- If there is no hint provided in the task, you can assume the method needs to be placed at the end of the test block.
|
|
46
|
+
- YOU NEED TO MANDATORILY USE "createTest" method to execute the task.
|
|
47
|
+
- Once the methods is placed in the right location with a task and playwright page provided, assume the task is done.
|
|
48
|
+
- You need to respond with file path, code block and updated code block where this method can be placed in order to accomplish the task`,
|
|
49
|
+
],
|
|
50
|
+
}, specPath, genConfig.options, false);
|
|
51
|
+
const createTestFilePath = suggestion?.updatedFiles[0] || "";
|
|
52
|
+
console.log("appending to existing test block");
|
|
53
|
+
console.log("updated test file path", createTestFilePath);
|
|
54
|
+
// if change suggested by LLM is in spec file
|
|
55
|
+
const isChangeInSpecFile = createTestFilePath.includes("spec.ts");
|
|
56
|
+
// if the file is not a spec file, add import for the test-gen
|
|
57
|
+
if (!isChangeInSpecFile) {
|
|
58
|
+
await fs_extra_1.default.writeFile(createTestFilePath, (0, web_1.addNewImport)(await fs_extra_1.default.readFile(createTestFilePath, "utf-8"), ["createTest"], "@empiricalrun/test-gen"));
|
|
59
|
+
}
|
|
60
|
+
const testFileContent = await fs_extra_1.default.readFile(specPath, "utf-8");
|
|
61
|
+
const { testBlock, testNode } = (0, web_1.getTypescriptTestBlock)(name, testFileContent);
|
|
62
|
+
const parentDescribe = (0, web_1.findFirstSerialDescribeBlock)(testNode);
|
|
63
|
+
// add test.only / describe.only to the spec file so that only that block is executed
|
|
64
|
+
const updatedTestFileContent = newContentsWithTestOnly(testFileContent, testBlock, testBlock, parentDescribe?.getText() || "");
|
|
65
|
+
await fs_extra_1.default.writeFile(specPath, isChangeInSpecFile
|
|
66
|
+
? (0, web_1.addNewImport)(updatedTestFileContent, ["createTest"], "@empiricalrun/test-gen")
|
|
67
|
+
: updatedTestFileContent);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Function to prepare test file for new scenarios for master agent to run
|
|
71
|
+
* @param {TestGenConfig} genConfig
|
|
72
|
+
*/
|
|
73
|
+
async function prepareFileForNewScenario(genConfig) {
|
|
26
74
|
const { specPath, testCase } = genConfig;
|
|
27
75
|
const { name, steps } = testCase;
|
|
28
|
-
|
|
76
|
+
console.log("creating new test block");
|
|
77
|
+
const mergedSteps = prepareBrowsingAgentTask(steps);
|
|
78
|
+
// TODO: this assumes that test code repo has `page` as the main entrypoint fixture
|
|
79
|
+
const testGenCodeBlock = createTestBlockForCreateTest(name, mergedSteps);
|
|
80
|
+
const existingContents = await fs_extra_1.default.readFile(specPath, "utf-8");
|
|
81
|
+
const newContents = `${existingContents}\n\n${testGenCodeBlock}`;
|
|
82
|
+
await fs_extra_1.default.writeFile(specPath, (0, web_1.addNewImport)(newContents, ["createTest"], "@empiricalrun/test-gen"));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Function to prepare test file for master agent to run
|
|
86
|
+
* @param {TestGenConfig} genConfig
|
|
87
|
+
* @return {*} {Promise<string>}
|
|
88
|
+
*/
|
|
89
|
+
async function prepareFileForMasterAgent(genConfig) {
|
|
90
|
+
const { specPath, testCase } = genConfig;
|
|
91
|
+
const { name } = testCase;
|
|
92
|
+
// check if the spec file exists
|
|
93
|
+
// if no then create a new file with test and expect imports
|
|
29
94
|
if (!fs_extra_1.default.existsSync(specPath)) {
|
|
30
95
|
await fs_extra_1.default.createFile(specPath);
|
|
31
96
|
const fileContentWithImports = (0, web_1.addNewImport)("", ["test", "expect"], (0, web_1.getFixtureImportPath)(specPath));
|
|
32
97
|
await fs_extra_1.default.writeFile(specPath, fileContentWithImports, "utf-8");
|
|
33
98
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const updatedTestBlock = (0, web_1.appendToTestBlock)(testBlock, createTestGenBlock(mergedSteps));
|
|
43
|
-
newContents = newContentsWithTestOnly(existingContents, testBlock, updatedTestBlock, parentDescribe);
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
logger.log("creating new test block");
|
|
47
|
-
// TODO: this assumes that test code repo has `page` as the main entrypoint fixture
|
|
48
|
-
const testGenCodeBlock = createTestBlockForCreateTest(name, mergedSteps);
|
|
49
|
-
newContents = `${existingContents}\n\n${testGenCodeBlock}`;
|
|
50
|
-
}
|
|
51
|
-
await fs_extra_1.default.writeFile(specPath, (0, web_1.addNewImport)(newContents, ["createTest"], "@empiricalrun/test-gen"));
|
|
99
|
+
let createTestFilePath = specPath;
|
|
100
|
+
const existingContents = await fs_extra_1.default.readFile(specPath, "utf-8");
|
|
101
|
+
const { testBlock } = (0, web_1.getTypescriptTestBlock)(name, existingContents);
|
|
102
|
+
if (testBlock) {
|
|
103
|
+
await prepareFileForUpdateScenario(genConfig);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
await prepareFileForNewScenario(genConfig);
|
|
52
107
|
}
|
|
108
|
+
return createTestFilePath;
|
|
53
109
|
}
|
|
54
|
-
exports.
|
|
110
|
+
exports.prepareFileForMasterAgent = prepareFileForMasterAgent;
|
|
55
111
|
function newContentsWithTestOnly(existingContents, originalTestBlock, updatedTestBlock, parentDescribeBlock) {
|
|
56
112
|
if (!parentDescribeBlock) {
|
|
57
113
|
const testMarkedAsOnly = updatedTestBlock.replace("test(", "test.only(");
|
|
@@ -95,9 +151,14 @@ async function injectPwLocatorGenerator(page) {
|
|
|
95
151
|
await Promise.all(scripts.map((s) => page.addScriptTag({ content: s })));
|
|
96
152
|
}
|
|
97
153
|
exports.injectPwLocatorGenerator = injectPwLocatorGenerator;
|
|
98
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Function to validate if the test file path are valid.
|
|
156
|
+
* @throws if there are any missing dependencies for master agent to run.
|
|
157
|
+
* @param {string} filePath
|
|
158
|
+
*/
|
|
159
|
+
function canRunMasterAgent(filePath) {
|
|
99
160
|
if (!fs_extra_1.default.existsSync(filePath)) {
|
|
100
|
-
throw new Error(`File for
|
|
161
|
+
throw new Error(`File for master agent to run not found: ${filePath}`);
|
|
101
162
|
}
|
|
102
163
|
const { dependencies: installedPackages } = JSON.parse((0, child_process_1.execSync)("npm ls --json").toString());
|
|
103
164
|
const hasPwTestInstalled = installedPackages["@playwright/test"];
|
|
@@ -115,7 +176,11 @@ function canRunBrowsingAgent(filePath) {
|
|
|
115
176
|
logger.warn(`Outdated @empiricalrun/test-gen package: expected ${latestTestGenVersion}, got ${testGenVersion}`);
|
|
116
177
|
}
|
|
117
178
|
}
|
|
118
|
-
exports.
|
|
179
|
+
exports.canRunMasterAgent = canRunMasterAgent;
|
|
180
|
+
/**
|
|
181
|
+
* function to read playwright config from the source repo
|
|
182
|
+
* @return {*} {Promise<PlaywrightTestConfig>}
|
|
183
|
+
*/
|
|
119
184
|
async function readPlaywrightConfig() {
|
|
120
185
|
const [lastDir] = process.cwd().split("/").reverse();
|
|
121
186
|
const playwrightConfig = (await api_1.default.require("./playwright.config.ts", `${process.cwd()}/${lastDir}`)).default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/run.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/run.ts"],"names":[],"mappings":"AAyBA,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAG7D,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,QAAQ,EAAE,CAAC,CAoJrB"}
|
|
@@ -142,6 +142,7 @@ async function generateTest(testCase, file, options) {
|
|
|
142
142
|
logger.log(`Trace: ${trace.getTraceUrl()}`);
|
|
143
143
|
generatedTestCases.push(testCase);
|
|
144
144
|
trace.update({ input: { testCase }, output: { response } });
|
|
145
|
+
await (0, llm_1.flushAllTraces)();
|
|
145
146
|
return generatedTestCases;
|
|
146
147
|
}
|
|
147
148
|
exports.generateTest = generateTest;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import { TestCase, TestGenConfigOptions } from "../../types";
|
|
2
|
-
|
|
2
|
+
type UpdatedTestCase = TestCase & {
|
|
3
|
+
updatedFiles: string[];
|
|
4
|
+
};
|
|
5
|
+
export declare function updateTest(testCase: TestCase, file: string, options: TestGenConfigOptions | undefined, logging?: boolean): Promise<UpdatedTestCase[]>;
|
|
6
|
+
export {};
|
|
3
7
|
//# sourceMappingURL=update-flow.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-flow.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/update-flow.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"update-flow.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/update-flow.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAqB7D,KAAK,eAAe,GAAG,QAAQ,GAAG;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAEF,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,GAAG,SAAS,EACzC,OAAO,GAAE,OAAc,GACtB,OAAO,CAAC,eAAe,EAAE,CAAC,CAoH5B"}
|
|
@@ -14,7 +14,8 @@ const constants_1 = require("../../constants");
|
|
|
14
14
|
const session_1 = require("../../session");
|
|
15
15
|
function extractInformation(input) {
|
|
16
16
|
const result = [];
|
|
17
|
-
|
|
17
|
+
// TODO: use better structure for this. Do not kill me for this please.
|
|
18
|
+
const regex = /<file_path>(.*?)<\/file_path>[\s\S]*?<old_code_block>([\s\S]*?)<\/old_code_block>[\s\S]*?<new_code_block>([\s\S]*?)<\/new_code_block>[\s\S]*?<change>([\s\S]*?)<\/change>/g;
|
|
18
19
|
let match;
|
|
19
20
|
while ((match = regex.exec(input)) !== null) {
|
|
20
21
|
const [, filePath, oldCode, newCode, reason] = match;
|
|
@@ -27,8 +28,8 @@ function extractInformation(input) {
|
|
|
27
28
|
}
|
|
28
29
|
return result;
|
|
29
30
|
}
|
|
30
|
-
async function updateTest(testCase, file, options) {
|
|
31
|
-
const logger = new logger_1.CustomLogger();
|
|
31
|
+
async function updateTest(testCase, file, options, logging = true) {
|
|
32
|
+
const logger = new logger_1.CustomLogger({ useReporter: logging });
|
|
32
33
|
const context = await (0, context_1.contextForGeneration)(file);
|
|
33
34
|
const { codePrompt, pomPrompt, testFileContent } = context;
|
|
34
35
|
const generatedTestCases = [];
|
|
@@ -38,7 +39,10 @@ async function updateTest(testCase, file, options) {
|
|
|
38
39
|
name: "update-test",
|
|
39
40
|
id: crypto_1.default.randomUUID(),
|
|
40
41
|
release: session.version,
|
|
41
|
-
tags: [
|
|
42
|
+
tags: [
|
|
43
|
+
options?.metadata.projectName || "",
|
|
44
|
+
options?.metadata.environment || "",
|
|
45
|
+
].filter((s) => !!s),
|
|
42
46
|
});
|
|
43
47
|
trace.event({
|
|
44
48
|
name: "collate-files-as-text",
|
|
@@ -59,30 +63,34 @@ async function updateTest(testCase, file, options) {
|
|
|
59
63
|
scenarioName: testCase.name,
|
|
60
64
|
scenarioSteps: testCase.steps.join("\n"),
|
|
61
65
|
scenarioFile: file,
|
|
62
|
-
},
|
|
66
|
+
}, 14);
|
|
63
67
|
promptSpan.end({ output: { instruction } });
|
|
64
68
|
const llm = new llm_1.LLM({
|
|
65
69
|
trace,
|
|
66
|
-
provider: options
|
|
67
|
-
defaultModel: options
|
|
68
|
-
providerApiKey: constants_1.MODEL_API_KEYS[options
|
|
70
|
+
provider: options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER,
|
|
71
|
+
defaultModel: options?.model || constants_1.DEFAULT_MODEL,
|
|
72
|
+
providerApiKey: constants_1.MODEL_API_KEYS[options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER],
|
|
69
73
|
});
|
|
70
74
|
const firstShotMessage = await llm.createChatCompletion({
|
|
71
75
|
messages: instruction,
|
|
72
76
|
modelParameters: {
|
|
73
77
|
...constants_1.DEFAULT_MODEL_PARAMETERS,
|
|
74
|
-
...options
|
|
78
|
+
...options?.modelParameters,
|
|
75
79
|
},
|
|
76
80
|
});
|
|
77
81
|
let response = firstShotMessage?.content || "";
|
|
78
82
|
logger.success("Test generated successfully!");
|
|
79
83
|
const fileChanges = extractInformation(response);
|
|
80
|
-
fileChanges.
|
|
81
|
-
if (fileChange.filePath
|
|
84
|
+
await Promise.allSettled(fileChanges.map(async (fileChange) => {
|
|
85
|
+
if (!fileChange.filePath) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const { testBlock: testBlockUpdate } = (0, web_1.getTypescriptTestBlock)(testCase?.name || "", fileChange.newCode || "");
|
|
89
|
+
if (testBlockUpdate) {
|
|
82
90
|
// assuming the test case getting updated
|
|
83
91
|
// maintaining the previous accuracy of the test case update
|
|
84
92
|
const readWriteFileSpan = trace.span({ name: "write-to-file" });
|
|
85
|
-
let contents = fs_extra_1.default.
|
|
93
|
+
let contents = await fs_extra_1.default.readFile(fileChange.filePath, "utf-8");
|
|
86
94
|
const [prependContent, strippedContent] = await (0, web_1.stripAndPrependImports)(fileChange.newCode, testCase?.name);
|
|
87
95
|
let updatedContent = prependContent + contents + `\n\n${strippedContent}`;
|
|
88
96
|
const { testBlock } = (0, web_1.getTypescriptTestBlock)(testCase?.name, contents);
|
|
@@ -91,25 +99,33 @@ async function updateTest(testCase, file, options) {
|
|
|
91
99
|
await fs_extra_1.default.writeFile(file, updatedContent, "utf-8");
|
|
92
100
|
readWriteFileSpan.end({ output: { updatedContent } });
|
|
93
101
|
trace.event({ name: "format-file" });
|
|
102
|
+
await (0, web_1.lintErrors)(fileChange.filePath);
|
|
94
103
|
await (0, web_1.formatCode)(fileChange.filePath);
|
|
95
|
-
logger.success(
|
|
104
|
+
logger.success(`${fileChange.filePath} file formatted successfully!`);
|
|
96
105
|
}
|
|
97
106
|
else {
|
|
98
107
|
// since we dont know what is getting updated,
|
|
99
|
-
// we believe that the patch is correct and contains few before and after lines
|
|
108
|
+
// we believe that the patch is correct and contains few before and after lines
|
|
109
|
+
// to make the change unique for search & replace
|
|
100
110
|
const readWriteFileSpan = trace.span({ name: "write-to-file" });
|
|
101
|
-
let contents = fs_extra_1.default.
|
|
111
|
+
let contents = await fs_extra_1.default.readFile(fileChange.filePath, "utf-8");
|
|
112
|
+
//TODO: move this to usage of ast blocks
|
|
102
113
|
contents = contents.replace(fileChange.oldCode, `\n\n${fileChange.newCode}`);
|
|
103
114
|
await fs_extra_1.default.writeFile(fileChange.filePath, contents, "utf-8");
|
|
104
115
|
readWriteFileSpan.end({ output: { contents } });
|
|
105
116
|
trace.event({ name: "format-file" });
|
|
117
|
+
await (0, web_1.lintErrors)(fileChange.filePath);
|
|
106
118
|
await (0, web_1.formatCode)(fileChange.filePath);
|
|
107
|
-
logger.success(
|
|
119
|
+
logger.success(`${fileChange.filePath} file formatted successfully!`);
|
|
108
120
|
}
|
|
109
|
-
});
|
|
121
|
+
}));
|
|
110
122
|
logger.log(`Trace: ${trace.getTraceUrl()}`);
|
|
111
|
-
generatedTestCases.push(
|
|
123
|
+
generatedTestCases.push({
|
|
124
|
+
...testCase,
|
|
125
|
+
updatedFiles: fileChanges.map((f) => f.filePath),
|
|
126
|
+
});
|
|
112
127
|
trace.update({ input: { testCase }, output: { response } });
|
|
128
|
+
await (0, llm_1.flushAllTraces)();
|
|
113
129
|
return generatedTestCases;
|
|
114
130
|
}
|
|
115
131
|
exports.updateTest = updateTest;
|
package/dist/bin/index.js
CHANGED
|
@@ -27,8 +27,11 @@ async function runAgent(testGenConfig) {
|
|
|
27
27
|
if (testGenConfig.options?.agent !== "code") {
|
|
28
28
|
// this assumes we have only one scenario in test config
|
|
29
29
|
logger.success(`Generating test using ${testGenConfig.options?.agent} agent`);
|
|
30
|
-
await (0, utils_1.
|
|
31
|
-
await (0, run_1.
|
|
30
|
+
const filePathToUpdate = await (0, utils_1.prepareFileForMasterAgent)(testGenConfig);
|
|
31
|
+
await (0, run_1.generateTestsUsingMasterAgent)({
|
|
32
|
+
testFilePath: specPath,
|
|
33
|
+
filePathToUpdate,
|
|
34
|
+
});
|
|
32
35
|
await new reporter_1.TestGenUpdatesReporter().reportGenAssets({
|
|
33
36
|
projectRepoName: testGenConfig.options.metadata.projectRepoName,
|
|
34
37
|
testName: testCase.name,
|
|
@@ -11,14 +11,14 @@ async function contextForGeneration(file) {
|
|
|
11
11
|
const ignoreFn = (0, ignore_1.default)();
|
|
12
12
|
if (fs_extra_1.default.existsSync(".gitignore")) {
|
|
13
13
|
// Not checking for nested gitignore
|
|
14
|
-
const gitignore = fs_extra_1.default.
|
|
14
|
+
const gitignore = (await fs_extra_1.default.readFile(".gitignore")).toString();
|
|
15
15
|
ignoreFn.add(gitignore);
|
|
16
16
|
}
|
|
17
17
|
const filter = ignoreFn.createFilter();
|
|
18
18
|
return {
|
|
19
19
|
codePrompt: await (0, fs_1.generatePromptFromDirectory)("./tests", filter),
|
|
20
20
|
pomPrompt: await (0, fs_1.generatePromptFromDirectory)("./pages", filter),
|
|
21
|
-
testFileContent: fs_extra_1.default.
|
|
21
|
+
testFileContent: await fs_extra_1.default.readFile(file, "utf-8"),
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
exports.contextForGeneration = contextForGeneration;
|
|
@@ -1,7 +1,39 @@
|
|
|
1
|
+
import { Node } from "ts-morph";
|
|
2
|
+
/**
|
|
3
|
+
* function to get the test block and test node for the scenario
|
|
4
|
+
* @export
|
|
5
|
+
* @param {string} scenarioName
|
|
6
|
+
* @param {string} content
|
|
7
|
+
* @return { testBlock: string; parentDescribe: string; } testBlock - the test block content, testNode - the test function node
|
|
8
|
+
*/
|
|
1
9
|
export declare function getTypescriptTestBlock(scenarioName: string, content: string): {
|
|
2
10
|
testBlock: string | undefined;
|
|
3
|
-
|
|
11
|
+
testNode: Node | undefined;
|
|
4
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Function to find the first 'describe' block configured with 'serial: true'
|
|
15
|
+
*
|
|
16
|
+
* e.g.
|
|
17
|
+
*
|
|
18
|
+
* test.describe("foo", () => {
|
|
19
|
+
*
|
|
20
|
+
* test.describe.configure({ mode: "serial" });
|
|
21
|
+
*
|
|
22
|
+
* test.describe("bar", () => {
|
|
23
|
+
*
|
|
24
|
+
* test.describe.configure({ mode: "serial" });
|
|
25
|
+
*
|
|
26
|
+
* })
|
|
27
|
+
*
|
|
28
|
+
* })
|
|
29
|
+
*
|
|
30
|
+
* for the above example,
|
|
31
|
+
* this function will return the first 'describe' block which is named "foo"
|
|
32
|
+
*
|
|
33
|
+
* @param {(Node | undefined)} node
|
|
34
|
+
* @return {(Node | undefined)}
|
|
35
|
+
*/
|
|
36
|
+
export declare function findFirstSerialDescribeBlock(node: Node | undefined): Node | undefined;
|
|
5
37
|
export declare function appendToTestBlock(testBlock: string, content: string): string;
|
|
6
38
|
export declare function validateTypescript(filePath: string): string[];
|
|
7
39
|
export declare function stripAndPrependImports(content: string, testName: string): Promise<(string | undefined)[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/bin/utils/platform/web/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/bin/utils/platform/web/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAqB,IAAI,EAAuB,MAAM,UAAU,CAAC;AAGxE;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,GACd;IACD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,EAAE,IAAI,GAAG,SAAS,CAAC;CAC5B,CAeA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,IAAI,GAAG,SAAS,GACrB,IAAI,GAAG,SAAS,CA4BlB;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAG5E;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAwD7D;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,mCAUjB;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,iBAShD;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,iBAQhD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,UAE5E;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,iBAMpD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,UAcpD;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,UA0CtB"}
|
|
@@ -3,28 +3,78 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.replaceCreateTestWithNewCode = exports.getFixtureImportPath = exports.removeTestOnly = exports.addNewImport = exports.formatCode = exports.lintErrors = exports.stripAndPrependImports = exports.validateTypescript = exports.appendToTestBlock = exports.getTypescriptTestBlock = void 0;
|
|
6
|
+
exports.replaceCreateTestWithNewCode = exports.getFixtureImportPath = exports.removeTestOnly = exports.addNewImport = exports.formatCode = exports.lintErrors = exports.stripAndPrependImports = exports.validateTypescript = exports.appendToTestBlock = exports.findFirstSerialDescribeBlock = exports.getTypescriptTestBlock = void 0;
|
|
7
7
|
const eslint_1 = require("eslint");
|
|
8
8
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
9
|
const prettier_1 = __importDefault(require("prettier"));
|
|
10
10
|
const ts_morph_1 = require("ts-morph");
|
|
11
11
|
const typescript_1 = __importDefault(require("typescript"));
|
|
12
|
+
/**
|
|
13
|
+
* function to get the test block and test node for the scenario
|
|
14
|
+
* @export
|
|
15
|
+
* @param {string} scenarioName
|
|
16
|
+
* @param {string} content
|
|
17
|
+
* @return { testBlock: string; parentDescribe: string; } testBlock - the test block content, testNode - the test function node
|
|
18
|
+
*/
|
|
12
19
|
function getTypescriptTestBlock(scenarioName, content) {
|
|
13
20
|
const project = new ts_morph_1.Project();
|
|
14
21
|
const sourceFile = project.createSourceFile("test.ts", content);
|
|
15
22
|
const testFunctionNode = sourceFile.getFirstDescendant((node) => !!(node.isKind(ts_morph_1.SyntaxKind.CallExpression) &&
|
|
16
23
|
node.getExpression().getText() === "test" &&
|
|
17
24
|
node.getArguments()[0]?.getText().includes(scenarioName)));
|
|
18
|
-
// TODO: support files that have 2 describe blocks with the same test name in them
|
|
19
|
-
const describeBlockNode = sourceFile.getFirstDescendant((node) => !!(node.isKind(ts_morph_1.SyntaxKind.CallExpression) &&
|
|
20
|
-
node.getExpression().getText() === "test.describe" &&
|
|
21
|
-
node.getText().includes(scenarioName)));
|
|
22
25
|
return {
|
|
23
26
|
testBlock: testFunctionNode?.getText(),
|
|
24
|
-
|
|
27
|
+
testNode: testFunctionNode,
|
|
25
28
|
};
|
|
26
29
|
}
|
|
27
30
|
exports.getTypescriptTestBlock = getTypescriptTestBlock;
|
|
31
|
+
/**
|
|
32
|
+
* Function to find the first 'describe' block configured with 'serial: true'
|
|
33
|
+
*
|
|
34
|
+
* e.g.
|
|
35
|
+
*
|
|
36
|
+
* test.describe("foo", () => {
|
|
37
|
+
*
|
|
38
|
+
* test.describe.configure({ mode: "serial" });
|
|
39
|
+
*
|
|
40
|
+
* test.describe("bar", () => {
|
|
41
|
+
*
|
|
42
|
+
* test.describe.configure({ mode: "serial" });
|
|
43
|
+
*
|
|
44
|
+
* })
|
|
45
|
+
*
|
|
46
|
+
* })
|
|
47
|
+
*
|
|
48
|
+
* for the above example,
|
|
49
|
+
* this function will return the first 'describe' block which is named "foo"
|
|
50
|
+
*
|
|
51
|
+
* @param {(Node | undefined)} node
|
|
52
|
+
* @return {(Node | undefined)}
|
|
53
|
+
*/
|
|
54
|
+
function findFirstSerialDescribeBlock(node) {
|
|
55
|
+
let currentNode = node;
|
|
56
|
+
// Traverse upwards until we find a 'describe' block with 'serial: true'
|
|
57
|
+
while (currentNode) {
|
|
58
|
+
const parentDescribe = currentNode.getFirstAncestorByKind(ts_morph_1.SyntaxKind.CallExpression);
|
|
59
|
+
if (parentDescribe) {
|
|
60
|
+
const isDescribe = parentDescribe.getFirstChild()?.getText() === "test.describe";
|
|
61
|
+
if (isDescribe) {
|
|
62
|
+
const configureCall = parentDescribe
|
|
63
|
+
.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
|
|
64
|
+
.find((call) => call.getText().includes("configure"));
|
|
65
|
+
if (configureCall) {
|
|
66
|
+
// Check if 'serial: true' exists
|
|
67
|
+
if (configureCall.getText().includes("serial")) {
|
|
68
|
+
return parentDescribe;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
currentNode = parentDescribe; // Move up the tree
|
|
74
|
+
}
|
|
75
|
+
return undefined; // Return undefined if no 'describe' with serial: true is found
|
|
76
|
+
}
|
|
77
|
+
exports.findFirstSerialDescribeBlock = findFirstSerialDescribeBlock;
|
|
28
78
|
function appendToTestBlock(testBlock, content) {
|
|
29
79
|
const updateTestBlock = testBlock.replace(/\}\)$/, `\n\n${content}\n\n })`);
|
|
30
80
|
return updateTestBlock;
|
|
@@ -96,7 +146,7 @@ async function lintErrors(filePath) {
|
|
|
96
146
|
});
|
|
97
147
|
const [result] = await eslint.lintFiles(filePath);
|
|
98
148
|
if (result?.output) {
|
|
99
|
-
fs_extra_1.default.
|
|
149
|
+
await fs_extra_1.default.writeFile(filePath, result.output);
|
|
100
150
|
}
|
|
101
151
|
}
|
|
102
152
|
exports.lintErrors = lintErrors;
|
|
@@ -107,7 +157,7 @@ async function formatCode(filePath) {
|
|
|
107
157
|
...prettierConfig,
|
|
108
158
|
filepath: filePath,
|
|
109
159
|
});
|
|
110
|
-
fs_extra_1.default.
|
|
160
|
+
await fs_extra_1.default.writeFile(filePath, formattedContent);
|
|
111
161
|
}
|
|
112
162
|
exports.formatCode = formatCode;
|
|
113
163
|
function addNewImport(contents, modules, pkg) {
|