@dev-blinq/cucumber_client 1.0.1197-dev → 1.0.1197-stage

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.
Files changed (39) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +220 -0
  2. package/bin/assets/preload/find_context.js +1 -1
  3. package/bin/assets/preload/recorderv3.js +75 -5
  4. package/bin/assets/preload/unique_locators.js +24 -3
  5. package/bin/assets/scripts/aria_snapshot.js +235 -0
  6. package/bin/assets/scripts/dom_attr.js +372 -0
  7. package/bin/assets/scripts/dom_element.js +0 -0
  8. package/bin/assets/scripts/dom_parent.js +185 -0
  9. package/bin/assets/scripts/event_utils.js +105 -0
  10. package/bin/assets/scripts/pw.js +7886 -0
  11. package/bin/assets/scripts/recorder.js +1147 -0
  12. package/bin/assets/scripts/snapshot_capturer.js +155 -0
  13. package/bin/assets/scripts/unique_locators.js +841 -0
  14. package/bin/assets/scripts/yaml.js +4770 -0
  15. package/bin/assets/templates/page_template.txt +2 -16
  16. package/bin/assets/templates/utils_template.txt +59 -7
  17. package/bin/client/cli_helpers.js +11 -13
  18. package/bin/client/code_cleanup/utils.js +42 -14
  19. package/bin/client/code_gen/code_inversion.js +48 -11
  20. package/bin/client/code_gen/index.js +3 -0
  21. package/bin/client/code_gen/page_reflection.js +37 -20
  22. package/bin/client/code_gen/playwright_codeget.js +153 -25
  23. package/bin/client/cucumber/feature.js +92 -35
  24. package/bin/client/cucumber/steps_definitions.js +109 -83
  25. package/bin/client/local_agent.js +6 -2
  26. package/bin/client/project.js +6 -2
  27. package/bin/client/recorderv3/bvt_recorder.js +272 -76
  28. package/bin/client/recorderv3/implemented_steps.js +69 -14
  29. package/bin/client/recorderv3/index.js +49 -7
  30. package/bin/client/recorderv3/network.js +299 -0
  31. package/bin/client/recorderv3/step_runner.js +183 -13
  32. package/bin/client/recorderv3/step_utils.js +155 -8
  33. package/bin/client/recorderv3/update_feature.js +58 -30
  34. package/bin/client/recording.js +7 -0
  35. package/bin/client/run_cucumber.js +16 -2
  36. package/bin/client/scenario_report.js +35 -8
  37. package/bin/client/test_scenario.js +0 -1
  38. package/bin/index.js +1 -0
  39. package/package.json +15 -8
@@ -1,6 +1,5 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "fs";
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
2
2
  import path from "path";
3
- import fs from "fs";
4
3
  import { generatePageName } from "../code_gen/playwright_codeget.js";
5
4
  import {
6
5
  executeStep,
@@ -9,26 +8,36 @@ import {
9
8
  getUtilsCodePage,
10
9
  loadStepDefinitions,
11
10
  saveRecording,
11
+ saveRoutes,
12
12
  } from "./step_utils.js";
13
13
  import { escapeString, getExamplesContent } from "./update_feature.js";
14
+ import fs from "fs";
15
+ import { locateDefinitionPath } from "../cucumber/steps_definitions.js";
16
+ import { tmpdir } from "os";
14
17
 
15
18
  // let copiedCodeToTemp = false;
16
19
  async function withAbort(fn, signal) {
17
20
  if (!signal) {
18
21
  return await fn();
19
22
  }
23
+ return new Promise((resolve, reject) => {
24
+ const abortHandler = () => reject(new Error("Aborted"));
25
+ signal.addEventListener("abort", abortHandler, { once: true });
20
26
 
21
- const abortPromise = new Promise((_, reject) => {
22
- signal.addEventListener("abort", () => reject(new Error("Aborted")), { once: true });
27
+ fn()
28
+ .then(resolve)
29
+ .catch(reject)
30
+ .finally(() => {
31
+ signal.removeEventListener("abort", abortHandler);
32
+ });
23
33
  });
24
-
25
- return await Promise.race([fn(), abortPromise]);
26
34
  }
27
35
  export class BVTStepRunner {
28
36
  #currentStepController;
29
37
  #port;
30
- constructor({ projectDir }) {
38
+ constructor({ projectDir, sendExecutionStatus }) {
31
39
  this.projectDir = projectDir;
40
+ this.sendExecutionStatus = sendExecutionStatus;
32
41
  }
33
42
  setRemoteDebugPort(port) {
34
43
  this.#port = port;
@@ -55,11 +64,12 @@ export class BVTStepRunner {
55
64
  // copiedCodeToTemp = true;
56
65
  }
57
66
 
58
- async writeTempFeatureFile({ step, parametersMap, tempFolderPath }) {
67
+ async writeTempFeatureFile({ step, parametersMap, tempFolderPath, tags }) {
59
68
  const tFilePath = path.join(tempFolderPath, "__temp.feature");
60
69
  // console.log(tFilePath);
61
70
  let tFileContent = `# temp feature file
62
71
  Feature: Temp feature
72
+ ${tags ? tags.join(" ") : ""}
63
73
  Scenario Outline: Temp Scenario
64
74
  Given ${escapeString(step.text)}
65
75
  `;
@@ -67,7 +77,135 @@ export class BVTStepRunner {
67
77
  writeFileSync(tFilePath, tFileContent);
68
78
  return tFilePath;
69
79
  }
70
- async runStep({ step, parametersMap, envPath }, bvtContext, options) {
80
+
81
+ executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText, config }, options) => {
82
+ const { skipAfter = true, skipBefore = true } = options || {};
83
+ const environment = {
84
+ ...process.env,
85
+ };
86
+
87
+ try {
88
+ const { loadConfiguration, loadSupport, runCucumber } = await import("@dev-blinq/cucumber-js/api");
89
+ const { runConfiguration } = await loadConfiguration(
90
+ {
91
+ provided: {
92
+ name: [scenario],
93
+ paths: [feature_file_path],
94
+ import: [path.join(tempFolderPath, "step_definitions", "**", "*.mjs")],
95
+ // format: ["bvt"],
96
+ },
97
+ },
98
+ { cwd: process.cwd(), env: environment }
99
+ );
100
+ // const files = glob.sync(path.join(tempFolderPath, "step_definitions", "**", "*.mjs"));
101
+ // console.log("Files found:", files);
102
+ const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
103
+ // console.log("found ", support.stepDefinitions.length, "step definitions");
104
+ // support.stepDefinitions.map((step) => {
105
+ // console.log("step", step.pattern);
106
+ // });
107
+
108
+ support.afterTestRunHookDefinitions = [];
109
+ if (skipAfter) {
110
+ // ignore afterAll/after hooks
111
+ support.afterTestCaseHookDefinitions = [];
112
+ }
113
+ if (skipBefore && !config.legacySyntax) {
114
+ // ignore beforeAll/before hooks
115
+ support.beforeTestCaseHookDefinitions = support.beforeTestCaseHookDefinitions.filter((hook) => {
116
+ return hook.uri.endsWith("utils.mjs");
117
+ });
118
+ }
119
+ support.beforeTestRunHookDefinitions = [];
120
+
121
+ let errorMesssage = null;
122
+ let info = null;
123
+ let errInfo = null;
124
+ const result = await runCucumber({ ...runConfiguration, support }, environment, (message) => {
125
+ if (message.testStepFinished) {
126
+ const { testStepFinished } = message;
127
+ const { testStepResult } = testStepFinished;
128
+ if (testStepResult.status === "FAILED" || testStepResult.status === "AMBIGUOUS") {
129
+ if (!errorMesssage) {
130
+ errorMesssage = testStepResult.message;
131
+ if (info) {
132
+ errInfo = info;
133
+ }
134
+ }
135
+ }
136
+ if (testStepResult.status === "UNDEFINED") {
137
+ if (!errorMesssage) {
138
+ errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
139
+ if (info) {
140
+ errInfo = info;
141
+ }
142
+ }
143
+ }
144
+ }
145
+ if (message.attachment) {
146
+ const attachment = message.attachment;
147
+ if (attachment.mediaType === "application/json" && attachment.body) {
148
+ const body = JSON.parse(attachment.body);
149
+ info = body.info;
150
+ const result = body.result;
151
+
152
+ if (result.status === "PASSED") {
153
+ this.sendExecutionStatus({
154
+ type: "cmdExecutionSuccess",
155
+ cmdId: body.cmdId,
156
+ });
157
+ } else {
158
+ this.sendExecutionStatus({
159
+ type: "cmdExecutionError",
160
+ cmdId: body.cmdId,
161
+ error: {
162
+ message: result.message,
163
+ info,
164
+ },
165
+ });
166
+ }
167
+ } else if (attachment.mediaType === "application/json+intercept-results" && attachment.body) {
168
+ const body = JSON.parse(attachment.body);
169
+ if (body) {
170
+ this.sendExecutionStatus({
171
+ type: "interceptResults",
172
+ interceptResults: body,
173
+ });
174
+ }
175
+ }
176
+ }
177
+ });
178
+ if (errorMesssage) {
179
+ const bvtError = new Error(errorMesssage);
180
+ Object.assign(bvtError, { info: errInfo });
181
+ throw bvtError;
182
+ }
183
+
184
+ return {
185
+ result,
186
+ info,
187
+ };
188
+ } catch (error) {
189
+ console.error("Error running cucumber-js", error);
190
+ throw error;
191
+ }
192
+ };
193
+
194
+ async runStep({ step, parametersMap, envPath, tags, config }, bvtContext, options) {
195
+ let cmdIDs = (step.commands || []).map((cmd) => cmd.cmdId);
196
+ if (bvtContext.web) {
197
+ bvtContext.web.getCmdId = () => {
198
+ if (cmdIDs.length === 0) {
199
+ cmdIDs = (step.commands || []).map((cmd) => cmd.cmdId);
200
+ }
201
+ const cId = cmdIDs.shift();
202
+ this.sendExecutionStatus({
203
+ type: "cmdExecutionStart",
204
+ cmdId: cId,
205
+ });
206
+ return cId;
207
+ };
208
+ }
71
209
  let codePage; // = getCodePage();
72
210
  // const tempFolderPath = process.env.tempFeaturesFolderPath;
73
211
  const __temp_features_FolderName = "__temp_features" + Math.random().toString(36).substring(2, 7);
@@ -96,7 +234,8 @@ export class BVTStepRunner {
96
234
  if (!existsSync(stepDefinitionFolderPath)) {
97
235
  mkdirSync(stepDefinitionFolderPath, { recursive: true });
98
236
  }
99
- const stepDefsFilePath = path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
237
+ const stepDefsFilePath = locateDefinitionPath(tempFolderPath, pageName);
238
+ //path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
100
239
  codePage = getCodePage(stepDefsFilePath);
101
240
  codePage = await saveRecording({ step, cucumberStep, codePage, projectDir: this.projectDir, stepsDefinitions });
102
241
  if (codePage) {
@@ -106,19 +245,49 @@ export class BVTStepRunner {
106
245
  if (!codePage) {
107
246
  codePage = getUtilsCodePage(this.projectDir);
108
247
  }
248
+ } else {
249
+ let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
250
+ if (process.env.TEMP_RUN === "true") {
251
+ // console.log("Save routes in temp folder for running:", routesPath);
252
+ if (existsSync(routesPath)) {
253
+ console.log("Removing existing temp_routes_folder:", routesPath);
254
+ rmSync(routesPath, { recursive: true });
255
+ }
256
+ mkdirSync(routesPath, { recursive: true });
257
+ console.log("Created temp_routes_folder:", routesPath);
258
+ saveRoutes({ step, folderPath: routesPath });
259
+ } else {
260
+ console.log("Saving routes in project directory:", this.projectDir);
261
+ if (existsSync(routesPath)) {
262
+ // remove the folder
263
+ try {
264
+ rmSync(routesPath, { recursive: true });
265
+ console.log("Removed temp_routes_folder:", routesPath);
266
+ } catch (error) {
267
+ console.error("Error removing temp_routes folder", error);
268
+ }
269
+ }
270
+ routesPath = path.join(this.projectDir, "data", "routes");
271
+ console.log("Saving routes to:", routesPath);
272
+ if (!existsSync(routesPath)) {
273
+ mkdirSync(routesPath, { recursive: true });
274
+ }
275
+ saveRoutes({ step, folderPath: routesPath });
276
+ }
109
277
  }
110
- const feature_file_path = await this.writeTempFeatureFile({ step, parametersMap, tempFolderPath });
278
+ const feature_file_path = await this.writeTempFeatureFile({ step, parametersMap, tempFolderPath, tags });
111
279
  // console.log({ feature_file_path, step_text: step.text });
112
280
 
113
281
  const stepExecController = new AbortController();
114
282
  this.#currentStepController = stepExecController;
115
- await withAbort(async () => {
116
- await stepsDefinitions.executeStepRemote(
283
+ const { result, info } = await withAbort(async () => {
284
+ return await this.executeStepRemote(
117
285
  {
118
286
  feature_file_path,
119
287
  tempFolderPath,
120
288
  stepText: step.text,
121
289
  scenario: "Temp Scenario",
290
+ config,
122
291
  },
123
292
  options
124
293
  );
@@ -128,5 +297,6 @@ export class BVTStepRunner {
128
297
  fs.rmSync(tempFolderPath, { recursive: true });
129
298
  }
130
299
  });
300
+ return { result, info };
131
301
  }
132
302
  }
@@ -1,14 +1,16 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
2
  import path from "path";
3
3
  import url from "url";
4
4
  import logger from "../../logger.js";
5
- import { CodePage } from "../code_gen/page_reflection.js";
5
+ import { CodePage, getAiConfig } from "../code_gen/page_reflection.js";
6
6
  import { generateCode, generatePageName } from "../code_gen/playwright_codeget.js";
7
7
  import { invertCodeToCommand } from "../code_gen/code_inversion.js";
8
8
  import { Step } from "../cucumber/feature.js";
9
- import { StepsDefinitions } from "../cucumber/steps_definitions.js";
9
+ import { locateDefinitionPath, StepsDefinitions } from "../cucumber/steps_definitions.js";
10
10
  import { Recording } from "../recording.js";
11
11
  import { generateApiCode } from "../code_gen/api_codegen.js";
12
+ import { tmpdir } from "os";
13
+ import { createHash } from "crypto";
12
14
 
13
15
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
14
16
 
@@ -69,19 +71,74 @@ function makeStepTextUnique(step, stepsDefinitions) {
69
71
  }
70
72
 
71
73
  export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions }) {
72
- // console.log("saveRecording", step.text);
74
+ let routesPath = path.join(tmpdir(), "blinq_temp_routes");
75
+
76
+ if (process.env.TEMP_RUN) {
77
+ if (existsSync(routesPath)) {
78
+ rmSync(routesPath, { recursive: true });
79
+ }
80
+ mkdirSync(routesPath, { recursive: true });
81
+ saveRoutes({ step, folderPath: routesPath });
82
+ } else {
83
+ if (existsSync(routesPath)) {
84
+ // remove the folder
85
+ try {
86
+ rmSync(routesPath, { recursive: true });
87
+ console.log("Removed temp_routes_folder:", routesPath);
88
+ } catch (error) {
89
+ console.error("Error removing temp_routes folder", error);
90
+ }
91
+ routesPath = path.join(projectDir, "data", "routes");
92
+ if (!existsSync(routesPath)) {
93
+ mkdirSync(routesPath, { recursive: true });
94
+ }
95
+ saveRoutes({ step, folderPath: routesPath });
96
+ }
97
+ }
98
+
73
99
  if (step.isImplementedWhileRecording && !process.env.TEMP_RUN) {
74
100
  return;
75
101
  }
102
+
76
103
  if (step.isImplemented && step.shouldOverride) {
77
104
  let stepDef = stepsDefinitions.findMatchingStep(step.text);
78
105
  codePage = getCodePage(stepDef.file);
79
106
  } else {
80
107
  const isUtilStep = makeStepTextUnique(step, stepsDefinitions);
108
+
81
109
  if (isUtilStep) {
82
110
  return;
83
111
  }
84
112
  }
113
+
114
+ if (process.env.TEMP_RUN === "true") {
115
+ console.log("Save routes in temp folder for running:", routesPath);
116
+ if (existsSync(routesPath)) {
117
+ console.log("Removing existing temp_routes_folder:", routesPath);
118
+ rmSync(routesPath, { recursive: true });
119
+ }
120
+ mkdirSync(routesPath, { recursive: true });
121
+ console.log("Created temp_routes_folder:", routesPath);
122
+ saveRoutes({ step, folderPath: routesPath });
123
+ } else {
124
+ console.log("Saving routes in project directory:", projectDir);
125
+ if (existsSync(routesPath)) {
126
+ // remove the folder
127
+ try {
128
+ rmSync(routesPath, { recursive: true });
129
+ console.log("Removed temp_routes_folder:", routesPath);
130
+ } catch (error) {
131
+ console.error("Error removing temp_routes folder", error);
132
+ }
133
+ }
134
+ routesPath = path.join(projectDir, "data", "routes");
135
+ console.log("Saving routes to:", routesPath);
136
+ if (!existsSync(routesPath)) {
137
+ mkdirSync(routesPath, { recursive: true });
138
+ }
139
+ saveRoutes({ step, folderPath: routesPath });
140
+ }
141
+
85
142
  cucumberStep.text = step.text;
86
143
  const recording = new Recording();
87
144
  const steps = step.commands;
@@ -108,6 +165,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
108
165
  isStaticToken,
109
166
  status,
110
167
  } = step.commands[0].value;
168
+
111
169
  const result = await generateApiCode(
112
170
  {
113
171
  url,
@@ -132,6 +190,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
132
190
  step.keyword,
133
191
  stepsDefinitions
134
192
  );
193
+
135
194
  if (!step.isImplemented) {
136
195
  stepsDefinitions.addStep({
137
196
  name: step.text,
@@ -139,6 +198,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
139
198
  source: "recorder",
140
199
  });
141
200
  }
201
+
142
202
  cucumberStep.methodName = result.methodName;
143
203
  return result.codePage;
144
204
  } else {
@@ -166,7 +226,13 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
166
226
  path
167
227
  );
168
228
  const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
169
- const stepResult = codePage.addCucumberStep(keyword, cucumberStep.getTemplate(), methodName, steps.length);
229
+ const stepResult = codePage.addCucumberStep(
230
+ keyword,
231
+ cucumberStep.getTemplate(),
232
+ methodName,
233
+ steps.length,
234
+ step.finalTimeout
235
+ );
170
236
 
171
237
  if (!step.isImplemented) {
172
238
  stepsDefinitions.addStep({
@@ -187,7 +253,17 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
187
253
 
188
254
  const getLocatorsJson = (file) => {
189
255
  if (!file) return {};
190
- const locatorsFilePath = file.replace(".mjs", ".json");
256
+ let locatorsFilePath = file.replace(".mjs", ".json");
257
+ const originLocatorsFilePath = locatorsFilePath;
258
+ const config = getAiConfig();
259
+ if (config && config.locatorsMetadataDir) {
260
+ // if config.locatorsMetadataDir is set, use it to create the file path
261
+ locatorsFilePath = path.join(config.locatorsMetadataDir, path.basename(locatorsFilePath));
262
+ if (!existsSync(locatorsFilePath)) {
263
+ // if the file does not exist in the config directory, use the original path
264
+ locatorsFilePath = originLocatorsFilePath;
265
+ }
266
+ }
191
267
  if (!existsSync(locatorsFilePath)) {
192
268
  return {};
193
269
  }
@@ -299,6 +375,7 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
299
375
  const steps = scenario.steps;
300
376
 
301
377
  const stepsDefinitions = new StepsDefinitions(projectDir);
378
+ const featureFolder = path.join(projectDir, "features");
302
379
  stepsDefinitions.load(false);
303
380
  // const parameters = scenario.parameters;
304
381
  // await saveRecordings({ steps, parameters, codePage, projectDir });
@@ -310,13 +387,41 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
310
387
  }
311
388
  }
312
389
  if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
390
+ let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
391
+ if (process.env.TEMP_RUN === "true") {
392
+ console.log("Save routes in temp folder for running:", routesPath);
393
+ if (existsSync(routesPath)) {
394
+ console.log("Removing existing temp_routes_folder:", routesPath);
395
+ rmSync(routesPath, { recursive: true });
396
+ }
397
+ mkdirSync(routesPath, { recursive: true });
398
+ console.log("Created temp_routes_folder:", routesPath);
399
+ saveRoutes({ step, folderPath: routesPath });
400
+ } else {
401
+ console.log("Saving routes in project directory:", projectDir);
402
+ if (existsSync(routesPath)) {
403
+ // remove the folder
404
+ try {
405
+ rmSync(routesPath, { recursive: true });
406
+ console.log("Removed temp_routes_folder:", routesPath);
407
+ } catch (error) {
408
+ console.error("Error removing temp_routes folder", error);
409
+ }
410
+ }
411
+ routesPath = path.join(projectDir, "data", "routes");
412
+ console.log("Saving routes to:", routesPath);
413
+ if (!existsSync(routesPath)) {
414
+ mkdirSync(routesPath, { recursive: true });
415
+ }
416
+ saveRoutes({ step, folderPath: routesPath });
417
+ }
313
418
  continue;
314
419
  }
315
420
  const cucumberStep = getCucumberStep({ step });
316
421
  const pageName = generatePageName(step.startFrame?.url ?? "default");
317
- const stepDefsFilePath = path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
422
+ const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
423
+ // path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
318
424
  let codePage = getCodePage(stepDefsFilePath);
319
-
320
425
  codePage = await saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions });
321
426
  if (!codePage) {
322
427
  continue;
@@ -328,3 +433,45 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
328
433
  }
329
434
  writeFileSync(utilsFilePath, utilsContent, "utf8");
330
435
  }
436
+
437
+ export function saveRoutes({ step, folderPath }) {
438
+ const routeItems = step.routeItems;
439
+ if (!routeItems || routeItems.length === 0) {
440
+ return;
441
+ }
442
+ const cucumberStep = getCucumberStep({ step });
443
+ const template = cucumberStep.getTemplate();
444
+ const stepNameHash = createHash("sha256").update(template).digest("hex");
445
+ console.log("Saving routes for step:", step.text, "with hash:", stepNameHash);
446
+
447
+ const routeItemsWithFilters = routeItems.map((routeItem) => {
448
+ const oldFilters = routeItem.filters;
449
+ const queryParamsObject = {};
450
+ oldFilters.queryParams.forEach((queryParam) => {
451
+ queryParamsObject[queryParam.key] = queryParam.value;
452
+ });
453
+ const newFilters = { path: oldFilters.path, method: oldFilters.method, queryParams: queryParamsObject };
454
+ return {
455
+ ...routeItem,
456
+ filters: newFilters,
457
+ };
458
+ });
459
+
460
+ const routesFilePath = path.join(folderPath, stepNameHash + ".json");
461
+ console.log("Routes file path:", routesFilePath);
462
+ const routesData = {
463
+ template,
464
+ routes: routeItemsWithFilters,
465
+ };
466
+ console.log("Routes data to save:", routesData);
467
+
468
+ if (!existsSync(folderPath)) {
469
+ mkdirSync(folderPath, { recursive: true });
470
+ }
471
+ try {
472
+ writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
473
+ console.log("Saved routes to", routesFilePath);
474
+ } catch (error) {
475
+ console.error("Failed to save routes to", routesFilePath, "Error:", error);
476
+ }
477
+ }